Compare commits

..

12 Commits

Author SHA1 Message Date
Jatinderjit Singh
c73f5aad16 feat(rules)!: drop frontend source URL fallback for related logs/traces and generator URL
Always derive the host portion of the rule generator URL and the
related_logs / related_traces annotations from SIGNOZ_ALERTMANAGER_SIGNOZ_EXTERNAL__URL.
Self-hosted deployments that have not set the env var will see the
default (http://localhost:8080) in alert notifications, which makes the
need to configure it surface clearly. Revert this commit to restore the
prior fallback behavior.

Refs SigNoz/engineering-pod#5055

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:48:28 +05:30
Jatinderjit Singh
88620b567e feat(rules): use alertmanager external URL for related logs/traces and generator URL
Plumbs SIGNOZ_ALERTMANAGER_SIGNOZ_EXTERNAL__URL through the rule manager so
the host portion of related_logs / related_traces annotations and the
rule generator URL (used in Slack/Teams/etc. notifications) come from the
operator-configured external URL instead of the frontend window.location
captured at rule-creation time. Frontend-supplied source URL is retained
as a fallback when the env var has not been set so existing self-hosted
deployments keep working.

Refs SigNoz/engineering-pod#5055

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-22 17:45:31 +05:30
swapnil-signoz
27dc996fd8 chore(integrations): make dot-metrics dashboards canonical, remove IsDotMetricsEnabled flag (#11406)
IsDotMetricsEnabled always returns true so the _dot.json variants were
always served. Replace each non-dot dashboard JSON with the dot content,
delete the _dot.json files, and remove the dead flag-check logic from
HydrateFileUris.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 10:14:48 +00:00
Karan Balani
83b25f3e9a fix(authdomain): sso enabled toggle value from nested config (#11402)
* fix(authdomain): read ssoEnabled from nested config path in enforce SSO column

* test(authdomain): assert enforce SSO toggle reflects nested config.ssoEnabled

* test(authdomain): drop unnecessary Switch mock

---------

Co-authored-by: Karan Balani <29383381+balanikaran@users.noreply.github.com>
2026-05-22 09:24:09 +00:00
Yunus M
67e4c4611c refactor: replace Ant Design Switch with Signoz UI Switch across mult… (#11223)
* refactor: replace Ant Design Switch with Signoz UI Switch across multiple components

* fix: update snapshot of failing test

* feat: update snapshot

* refactor: update imports to use Signoz UI Switch from the new path across multiple components

* refactor: update banned components to use Signoz UI imports for Typography and Switch

* refactor: replace Ant Design Switch with Signoz UI Switch
2026-05-22 08:50:41 +00:00
SagarRajput-7
7274421895 chore: fga ui feedbacks (#11403)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* chore: updated the signozhq version and removed ts-expect-error from button

* chore: renamed authz test with authz.test.tsx

* chore: remove error from useAuthZ public API and fallbackOnError from GuardAuthZ

* chore: updated test cases

* chore: updated test cases

* chore: restore error to useAuthZ API with fail-open default in GuardAuthZ

* chore: updated test cases
2026-05-21 23:49:44 +00:00
SagarRajput-7
9c6656d6b9 fix(user-info): surfaced errors for reset password and fixed issues (#11389)
* fix(user-info): surfaced errors for reset password and fixed issues

* fix(user-info): removed notification from atnd and used toast and showerrormodal in userinfo

* fix(user-info): refactor and added tests

* fix(user-info): code refactor
2026-05-21 17:24:31 +00:00
Nikhil Mantri
5c54a2537c chore: arrays non-nullable (#11388) 2026-05-21 17:22:25 +00:00
Nikhil Mantri
bf201710a7 feat(infra-monitoring): allow order by primary name column in v2 apis (#11264)
Some checks failed
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
* chore: baseline setup

* chore: endpoint detail update

* chore: added logic for hosts v3 api

* fix: bug fix

* chore: disk usage

* chore: added validate function

* chore: added some unit tests

* chore: return status as a string

* chore: yarn generate api

* chore: removed isSendingK8sAgentsMetricsCode

* chore: moved funcs

* chore: added validation on order by

* chore: added pods list logic

* chore: updated openapi yml

* chore: updated spec

* chore: pods api meta start time

* chore: nil pointer check

* chore: nil pointer dereference fix in req.Filter

* chore: added temporalities of metrics

* chore: added pods metrics temporality

* chore: unified composite key function

* chore: code improvements

* chore: added pods list api updates

* chore: hostStatusNone added for clarity that this field can be left empty as well in payload

* chore: yarn generate api

* chore: return errors from getMetadata and lint fix

* chore: return errors from getMetadata and lint fix

* chore: added hostName logic

* chore: modified getMetadata query

* chore: add type for response and files rearrange

* chore: warnings added passing from queryResponse warning to host lists response struct

* chore: added better metrics existence check

* chore: added a TODO remark

* chore: added required metrics check

* chore: distributed samples table to local table change for get metadata

* chore: frontend fix

* chore: endpoint correction

* chore: endpoint modification openapi

* chore: escape backtick to prevent sql injection

* chore: rearrage

* chore: improvements

* chore: validate order by to validate function

* chore: improved description

* chore: added TODOs and made filterByStatus a part of filter struct

* chore: ignore empty string hosts in get active hosts

* feat(infra-monitoring): v2 hosts list - return counts of active & inactive hosts for custom group by attributes (#10956)

* chore: add functionality for showing active and inactive counts in custom group by

* chore: bug fix

* chore: added subquery for active and total count

* chore: ignore empty string hosts in get active hosts

* fix: sinceUnixMilli for determining active hosts compute once per request

* chore: refactor code

* chore: rename HostsList -> ListHosts

* chore: rearrangement

* chore: inframonitoring types renaming

* chore: added types package

* chore: file structure further breakdown for clarity

* chore: comments correction

* chore: removed temporalities

* chore: pods code restructuring

* chore: comments resolve

* chore: added json tag required: true

* chore: removed pod metric temporalities

* chore: removed internal server error

* chore: added status unauthorized

* chore: remove a defensive nil map check, the function ensure non-nil map when err nil

* chore: cleanup and rename

* chore: make sort stable in case of tiebreaker by comparing composite group by keys

* chore: regen api client for inframonitoring

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: added phase counts feature

* chore: added queries for pod phase counts in custom group by

* chore: added required tags

* chore: added support for pod phase unknown

* chore: removed pods - order by phase

* chore: improved api description to document -1 as no data in numeric fields

* fix: rebase fixes

* chore: added unknown phase count

* fix: isPodUIDInGroupBy in buildPodRecords

* chore: 3 cte --> 2 cte

* chore: pod phase with local table of time series as counts

* chore: comment correction

* chore: corrected comment

* chore: value column for samples table added

* chore: removed query G for phase counts

* chore: rename variable

* chore: added PodPhaseNum constants to types

* feat(infra-monitoring): v2 pods list apis - phase counts when custom grouping (#11088)

* chore: added phase counts feature

* chore: added queries for pod phase counts in custom group by

* chore: added unknown phase count

* fix: isPodUIDInGroupBy in buildPodRecords

* chore: 3 cte --> 2 cte

* chore: pod phase with local table of time series as counts

* chore: comment correction

* chore: corrected comment

* chore: value column for samples table added

* chore: removed query G for phase counts

* chore: rename variable

* chore: added PodPhaseNum constants to types

* chore: nodes list v2 full blown

* chore: metadata fix

* chore: updated comment

* chore: namespaces code

* chore: v2 nodes api

* chore: rename

* chore: v2 clusters list api

* chore: namespaces code

* chore: rename

* chore: review clusters PR

* chore: pvcs code added

* chore: updated endpoint and spec

* chore: pvcs todo

* chore: added condition

* chore: added filter

* chore: added code for deployments

* chore: query nit

* chore: statefulsets code added

* chore: base filter added

* chore: added base deployments change

* chore: added base condition

* chore: v2 jobs list api added

* chore: added daemonsets api

* chore: added pod phase counts

* chore: for pods and nodes, replace none with no_data

* chore: node and pod counts structs added

* chore: namespace record uses PodCountsByPhase

* chore: cluster record uses PodCountsByPhase, NodeCountsByReadiness

* chore: deployment record uses PodCountsByPhase

* chore: statefulset record uses PodCountsByPhase

* chore: job record uses PodCountsByPhase

* chore: daemonset record uses PodCountsByPhase

* chore: added remaining metrics to check

* chore: metrics existence check

* chore: statefulset metrics added

* chore: added jobs metrics

* chore: added metrics

* chore: feature added

* chore: cosmetic changes

* chore: replaced common order by key with entity specific attr key

* chore: moved paginateByName to types and added unit tests

* chore: added pageGroups

* chore: assert added instead of require

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ashwin Bhatkal <ashwin96@gmail.com>
2026-05-21 14:55:14 +00:00
Aditya Singh
a5adc52276 feat(trace-details): promote ExpandableValue to periscope + dropdown z-index fix (#11393)
* feat: add expandable value component around status message

* feat: add minor change

* feat: style fix

* feat: remove comment
2026-05-21 12:19:51 +00:00
Aditya Singh
5ddcf33811 Fix drilldown on service details page (#11338)
* feat: fix drilldown on service details page

* feat: hide edit btn if no dashboard
2026-05-21 10:51:36 +00:00
SagarRajput-7
c0fe996e7a feat(no-auth): added no auth frontend setup (#11218)
* feat(no-auth): wire preflight global-config check and gate AppRoutes render & cleanAuthStorage util

* feat(no-auth): setup interceptor and ui hiding for no auth mode

* feat(no-auth): replace hide pattern with disable+tooltip via NoAuthGuard

* feat(no-auth): replace localstorage approach with module-level singleton

* feat(no-auth): added no-auth announcement banner and added authguard on sa page

* feat(no-auth): added more authguard

* feat(no-auth): fixes and refactor after rebase

* feat(no-auth): added noauth guard at more places and added tests

* feat(no-auth): refactor and feedback fix

* feat(no-auth): added noauth guard at more places and refactor

* feat(no-auth): changed banner text and code refactor

* feat(no-auth): added doc link under learn more text

* feat(no-auth): removed ui guards and special handling for the no auth mode'

* feat(no-auth): updated test case
2026-05-21 08:38:51 +00:00
206 changed files with 3580 additions and 21673 deletions

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"log/slog"
"net/url"
"github.com/spf13/cobra"
@@ -118,8 +119,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(_ sqlstore.SQLStore, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
return implcloudintegration.NewModule(), nil
},
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, nil, nil))
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser, externalURL *url.URL) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, externalURL, nil, nil))
},
)
if err != nil {

View File

@@ -3,6 +3,7 @@ package main
import (
"context"
"log/slog"
"net/url"
"time"
"github.com/spf13/cobra"
@@ -181,8 +182,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
},
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, eerules.PrepareTaskFunc, eerules.TestNotification))
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser, externalURL *url.URL) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, externalURL, eerules.PrepareTaskFunc, eerules.TestNotification))
},
)
if err != nil {

View File

@@ -2689,7 +2689,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesClusterRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
@@ -2759,7 +2758,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesDaemonSetRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
@@ -2829,7 +2827,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesDeploymentRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
@@ -2908,7 +2905,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesHostRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
@@ -2984,7 +2980,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesJobRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
@@ -3032,7 +3027,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesNamespaceRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
@@ -3110,7 +3104,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesNodeRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
@@ -3209,7 +3202,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesPodRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
@@ -3554,7 +3546,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesStatefulSetRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
@@ -3615,7 +3606,6 @@ components:
records:
items:
$ref: '#/components/schemas/InframonitoringtypesVolumeRecord'
nullable: true
type: array
requiredMetricsCheck:
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'

View File

@@ -39,6 +39,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithRuleStateHistoryModule(opts.ManagerOpts.RuleStateHistoryModule),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
@@ -63,6 +64,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithRuleStateHistoryModule(opts.ManagerOpts.RuleStateHistoryModule),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
@@ -87,6 +89,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithRuleStateHistoryModule(opts.ManagerOpts.RuleStateHistoryModule),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
return task, err
@@ -146,6 +149,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
@@ -167,6 +171,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
@@ -186,6 +191,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, error) {
baserules.WithSQLStore(opts.SQLStore),
baserules.WithQueryParser(opts.ManagerOpts.QueryParser),
baserules.WithMetadataStore(opts.ManagerOpts.MetadataStore),
baserules.WithExternalURL(opts.ManagerOpts.ExternalURL),
)
if err != nil {
slog.Error("failed to prepare a new anomaly rule for test", "name", alertname, errors.Attr(err))

View File

@@ -49,7 +49,7 @@
"@signozhq/design-tokens": "2.1.4",
"@signozhq/icons": "0.4.0",
"@signozhq/resizable": "0.0.2",
"@signozhq/ui": "0.0.19",
"@signozhq/ui": "0.0.21",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",

View File

@@ -14,7 +14,9 @@
*/
const BANNED_COMPONENTS = {
Typography: 'Use @signozhq/ui Typography instead of antd Typography.',
Typography:
'Use @signozhq/ui/typography Typography instead of antd Typography.',
Switch: 'Use @signozhq/ui/switch Switch instead of antd Switch.',
Badge: 'Use @signozhq/ui/badge instead of antd Badge.',
};

View File

@@ -77,8 +77,8 @@ importers:
specifier: 0.0.2
version: 0.0.2(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@signozhq/ui':
specifier: 0.0.19
version: 0.0.19(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)
specifier: 0.0.21
version: 0.0.21(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)
'@tanstack/react-table':
specifier: 8.21.3
version: 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -3269,8 +3269,8 @@ packages:
peerDependencies:
react: ^18.2.0
'@signozhq/ui@0.0.19':
resolution: {integrity: sha512-2q6aRxN/PR4PlR2xJZAREEuvLPiDFggfFKzCW2Z5vHVVbrgnvZHWD1jPUuwszfEg0ceH3UvkwqceO7wN4uRJAA==}
'@signozhq/ui@0.0.21':
resolution: {integrity: sha512-uLM3Vqwxlk2USXbwtb3qRLpjZR9b9QSHFQq/jtcfYNMDmIE/sNjSj0nRkEhX4RqqRgsLRt2PVA33aeWxDOLO3g==}
peerDependencies:
'@signozhq/icons': 0.3.0
react: ^18.2.0
@@ -3851,27 +3851,6 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
'@webassemblyjs/ast@1.14.1':
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
'@webassemblyjs/floating-point-hex-parser@1.13.2':
resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==}
'@webassemblyjs/helper-api-error@1.13.2':
resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==}
'@webassemblyjs/helper-buffer@1.14.1':
resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==}
'@webassemblyjs/helper-numbers@1.13.2':
resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==}
'@webassemblyjs/helper-wasm-bytecode@1.13.2':
resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==}
'@webassemblyjs/helper-wasm-section@1.14.1':
resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==}
'@xmldom/xmldom@0.8.13':
resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
engines: {node: '>=10.0.0'}
@@ -12034,7 +12013,7 @@ snapshots:
- react-dom
- tailwindcss
'@signozhq/ui@0.0.19(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)':
'@signozhq/ui@0.0.21(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)':
dependencies:
'@chenglou/pretext': 0.0.5
'@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)

View File

@@ -166,6 +166,7 @@ function createMockAppContext(
userPreferences: [],
hostsData: null,
isLoggedIn: true,
isPreflightLoading: false,
org: [{ createdAt: 0, id: 'org-id', displayName: 'Test Org' }],
isFetchingUser: false,
isFetchingActiveLicense: false,

View File

@@ -59,6 +59,7 @@ function App(): JSX.Element {
isLoggedIn: isLoggedInState,
featureFlags,
org,
isPreflightLoading,
} = useAppContext();
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
const isAIAssistantEnabled = useIsAIAssistantEnabled();
@@ -386,6 +387,10 @@ function App(): JSX.Element {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isCloudUser, isEnterpriseSelfHostedUser]);
if (isPreflightLoading) {
return <Spinner tip="Loading..." />;
}
// if the user is in logged in state
if (isLoggedInState) {
// if the setup calls are loading then return a spinner

View File

@@ -0,0 +1,72 @@
import axios from 'axios';
import { getIsNoAuthMode } from 'utils/noAuthMode';
import { interceptorRejected } from '../index';
jest.mock('utils/noAuthMode', () => ({
getIsNoAuthMode: jest.fn(),
}));
jest.mock('api/v2/sessions/rotate/post', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('AppRoutes/utils', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('../utils', () => ({
Logout: jest.fn(),
}));
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
const post = require('api/v2/sessions/rotate/post').default;
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
const { Logout } = require('../utils');
describe('interceptorRejected — no-auth mode', () => {
beforeEach(() => {
jest.clearAllMocks();
jest.spyOn(axios, 'isAxiosError').mockReturnValue(true);
});
it('does NOT call rotate or Logout when no-auth mode is enabled on 401', async () => {
(getIsNoAuthMode as jest.Mock).mockReturnValue(true);
const error = {
isAxiosError: true,
response: {
status: 401,
config: { url: '/dashboards', method: 'get' },
},
config: { url: '/dashboards', headers: {} },
};
await interceptorRejected(error as any).catch(() => {});
expect(post).not.toHaveBeenCalled();
expect(Logout).not.toHaveBeenCalled();
});
it('DOES attempt rotate when no-auth mode is disabled on 401', async () => {
(getIsNoAuthMode as jest.Mock).mockReturnValue(false);
(post as jest.Mock).mockResolvedValue({
data: { accessToken: 'a', refreshToken: 'b' },
});
const error = {
isAxiosError: true,
response: {
status: 401,
config: { url: '/dashboards', method: 'get' },
},
config: { url: '/dashboards', headers: {} },
};
await interceptorRejected(error as any).catch(() => {});
expect(post).toHaveBeenCalled();
});
});

View File

@@ -3488,9 +3488,9 @@ export interface InframonitoringtypesClustersDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesClusterRecordDTO[] | null;
records: InframonitoringtypesClusterRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
@@ -3566,9 +3566,9 @@ export interface InframonitoringtypesDaemonSetsDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesDaemonSetRecordDTO[] | null;
records: InframonitoringtypesDaemonSetRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
@@ -3644,9 +3644,9 @@ export interface InframonitoringtypesDeploymentsDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesDeploymentRecordDTO[] | null;
records: InframonitoringtypesDeploymentRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
@@ -3730,9 +3730,9 @@ export interface InframonitoringtypesHostsDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesHostRecordDTO[] | null;
records: InframonitoringtypesHostRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
@@ -3816,9 +3816,9 @@ export interface InframonitoringtypesJobsDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesJobRecordDTO[] | null;
records: InframonitoringtypesJobRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
@@ -3866,9 +3866,9 @@ export interface InframonitoringtypesNamespacesDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesNamespaceRecordDTO[] | null;
records: InframonitoringtypesNamespaceRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
@@ -3933,9 +3933,9 @@ export interface InframonitoringtypesNodesDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesNodeRecordDTO[] | null;
records: InframonitoringtypesNodeRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
@@ -4017,9 +4017,9 @@ export interface InframonitoringtypesPodsDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesPodRecordDTO[] | null;
records: InframonitoringtypesPodRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
@@ -4437,9 +4437,9 @@ export interface InframonitoringtypesStatefulSetsDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesStatefulSetRecordDTO[] | null;
records: InframonitoringtypesStatefulSetRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer
@@ -4506,9 +4506,9 @@ export interface InframonitoringtypesVolumesDTO {
*/
endTimeBeforeRetention: boolean;
/**
* @type array,null
* @type array
*/
records: InframonitoringtypesVolumeRecordDTO[] | null;
records: InframonitoringtypesVolumeRecordDTO[];
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
/**
* @type integer

View File

@@ -13,6 +13,7 @@ import { Events } from 'constants/events';
import { LOCALSTORAGE } from 'constants/localStorage';
import { getBasePath } from 'utils/basePath';
import { eventEmitter } from 'utils/getEventEmitter';
import { getIsNoAuthMode } from 'utils/noAuthMode';
import apiV1, { apiAlertManager, apiV2, apiV3, apiV4, apiV5 } from './apiV1';
import { Logout } from './utils';
@@ -108,7 +109,10 @@ export const interceptorRejected = async (
if (axios.isAxiosError(value) && value.response) {
const { response } = value;
const isNoAuthMode = getIsNoAuthMode();
if (
!isNoAuthMode &&
response.status === 401 &&
// if the session rotate call or the create session errors out with 401 or the delete sessions call returns 401 then we do not retry!
response.config.url !== '/sessions/rotate' &&
@@ -140,16 +144,20 @@ export const interceptorRejected = async (
return await Promise.resolve(reResponse);
} catch (error) {
if ((error as AxiosError)?.response?.status === 401) {
Logout();
void Logout();
}
}
} catch (error) {
Logout();
void Logout();
}
}
if (response.status === 401 && response.config.url === '/sessions/rotate') {
Logout();
if (
!isNoAuthMode &&
response.status === 401 &&
response.config.url === '/sessions/rotate'
) {
void Logout();
}
}
return await Promise.reject(value);

View File

@@ -137,7 +137,6 @@ function CreateServiceAccountModal(): JSX.Element {
<AuthZTooltip checks={[SACreatePermission]}>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form="create-sa-form"
variant="solid"
color="primary"

View File

@@ -11,9 +11,6 @@ import { GuardAuthZ } from './GuardAuthZ';
describe('GuardAuthZ', () => {
const TestChild = (): ReactElement => <div>Protected Content</div>;
const LoadingFallback = (): ReactElement => <div>Loading...</div>;
const ErrorFallback = (error: Error): ReactElement => (
<div>Error occurred: {error.message}</div>
);
const NoPermissionFallback = (_response: {
requiredPermissionName: BrandedPermission;
}): ReactElement => <div>Access denied</div>;
@@ -90,40 +87,28 @@ describe('GuardAuthZ', () => {
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should render fallbackOnError when API error occurs', async () => {
const errorMessage = 'Internal Server Error';
it('should render children when API error occurs and no fallbackOnError provided (fail open)', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: errorMessage }));
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
}),
);
render(
<GuardAuthZ relation="read" object="role:*" fallbackOnError={ErrorFallback}>
<GuardAuthZ relation="read" object="role:*">
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(screen.getByText(/Error occurred:/)).toBeInTheDocument();
expect(screen.getByText('Protected Content')).toBeInTheDocument();
});
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should pass error object to fallbackOnError function', async () => {
const errorMessage = 'Network request failed';
let receivedError: Error | null = null;
const errorFallbackWithCapture = (error: Error): ReactElement => {
receivedError = error;
return <div>Captured error: {error.message}</div>;
};
it('should render fallbackOnError when API error occurs and fallbackOnError is provided', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: errorMessage }));
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
}),
);
@@ -131,35 +116,14 @@ describe('GuardAuthZ', () => {
<GuardAuthZ
relation="read"
object="role:*"
fallbackOnError={errorFallbackWithCapture}
fallbackOnError={<div>Custom error fallback</div>}
>
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(receivedError).not.toBeNull();
});
expect(receivedError).toBeInstanceOf(Error);
expect(screen.getByText(/Captured error:/)).toBeInTheDocument();
});
it('should render null when error occurs and no fallbackOnError provided', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
}),
);
const { container } = render(
<GuardAuthZ relation="read" object="role:*">
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(container.firstChild).toBeNull();
expect(screen.getByText('Custom error fallback')).toBeInTheDocument();
});
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();

View File

@@ -12,7 +12,7 @@ export type GuardAuthZProps<R extends AuthZRelation> = {
relation: R;
object: AuthZObject<R>;
fallbackOnLoading?: JSX.Element;
fallbackOnError?: (error: Error) => JSX.Element;
fallbackOnError?: JSX.Element;
fallbackOnNoPermissions?: (response: {
requiredPermissionName: BrandedPermission;
}) => JSX.Element;
@@ -35,7 +35,7 @@ export function GuardAuthZ<R extends AuthZRelation>({
}
if (error) {
return fallbackOnError?.(error) ?? null;
return fallbackOnError ?? children;
}
if (!permissions?.[permission]?.isGranted) {

View File

@@ -4,7 +4,8 @@ import { useSelector } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { Color } from '@signozhq/design-tokens';
import { Button, Switch } from 'antd';
import { Button } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
@@ -125,9 +126,8 @@ function ShareURLModal(): JSX.Element {
<Info size={14} color={Color.BG_AMBER_600} />
)}
<Switch
checked={enableAbsoluteTime}
value={enableAbsoluteTime}
disabled={!isValidateRelativeTime}
size="small"
onChange={(): void => {
setEnableAbsoluteTime((prev) => !prev);
}}

View File

@@ -0,0 +1,13 @@
.banner {
height: var(--spacing-20);
a {
color: var(--callout-warning-title);
text-decoration: underline;
&:hover {
color: var(--callout-warning-title);
opacity: 0.8;
}
}
}

View File

@@ -0,0 +1,26 @@
import { PersistedAnnouncementBanner } from '@signozhq/ui/announcement-banner';
import styles from './NoAuthBanner.module.scss';
export function NoAuthBanner(): JSX.Element {
return (
<PersistedAnnouncementBanner
type="warning"
storageKey="no-auth-banner-v1"
testId="no-auth-banner"
className={styles.banner}
>
Impersonation mode: authentication is disabled. Anyone with access to this
instance has admin privileges.{' '}
<a
href="https://signoz.io/docs/manage/administrator-guide/configuration/impersonation-mode/"
target="_blank"
rel="noreferrer"
>
Learn more
</a>
</PersistedAnnouncementBanner>
);
}
export default NoAuthBanner;

View File

@@ -0,0 +1,24 @@
import { render, screen } from 'tests/test-utils';
import { NoAuthBanner } from '../NoAuthBanner';
describe('NoAuthBanner', () => {
it('renders the no-auth message', () => {
render(<NoAuthBanner />);
expect(
screen.getByText(/Impersonation mode: authentication is disabled/i),
).toBeInTheDocument();
});
it('renders with the warning test id', () => {
render(<NoAuthBanner />);
expect(screen.getByTestId('no-auth-banner')).toBeInTheDocument();
});
it('renders a docs link that opens in a new tab', () => {
render(<NoAuthBanner />);
const link = screen.getByRole('link', { name: /learn more/i });
expect(link).toHaveAttribute('target', '_blank');
expect(link).toHaveAttribute('rel', 'noreferrer');
});
});

View File

@@ -14,7 +14,8 @@ import {
ComboboxList,
ComboboxTrigger,
} from '@signozhq/ui/combobox';
import { Skeleton, Switch, Tooltip } from 'antd';
import { Skeleton, Tooltip } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import getLocalStorageKey from 'api/browser/localstorage/get';
import setLocalStorageKey from 'api/browser/localstorage/set';
@@ -281,9 +282,8 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
<div className="api-quick-filters-header">
<Typography.Text>Show IP addresses</Typography.Text>
<Switch
size="small"
style={{ marginLeft: 'auto' }}
checked={showIP ?? true}
value={showIP ?? true}
onChange={(checked): void => {
logEvent('API Monitoring: Show IP addresses clicked', {
showIP: checked,

View File

@@ -4,7 +4,8 @@ import type {
TableColumnsType as ColumnsType,
TableColumnType as ColumnType,
} from 'antd';
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
import { Button, Dropdown, Flex, MenuProps } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import logEvent from 'api/common/logEvent';
import LaunchChatSupport from 'components/LaunchChatSupport/LaunchChatSupport';
import { useSafeNavigate } from 'hooks/useSafeNavigate';
@@ -60,9 +61,7 @@ function DynamicColumnTable({
const onToggleHandler =
(index: number, column: ColumnGroupType<any> | ColumnType<any>) =>
(checked: boolean, event: React.MouseEvent<HTMLButtonElement>): void => {
event.stopPropagation();
(checked: boolean): void => {
if (shouldSendAlertsLogEvent) {
logEvent('Alert: Column toggled', {
column: column?.title,
@@ -88,10 +87,14 @@ function DynamicColumnTable({
const items: MenuProps['items'] =
dynamicColumns?.map((column, index) => ({
label: (
<div className="dynamicColumnsTable-items">
<div
className="dynamicColumnsTable-items"
onClick={(e): void => e.stopPropagation()}
role="presentation"
>
<div>{column.title?.toString()}</div>
<Switch
checked={columnsData?.findIndex((c) => c.key === column.key) !== -1}
value={columnsData?.findIndex((c) => c.key === column.key) !== -1}
onChange={onToggleHandler(index, column)}
/>
</div>

View File

@@ -127,7 +127,6 @@ function KeyFormPhase({
>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form={FORM_ID}
variant="solid"
color="primary"

View File

@@ -190,7 +190,6 @@ function EditKeyForm({
>
<Button
type="submit"
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
form={FORM_ID}
variant="solid"
color="primary"

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useMemo } from 'react';
import { KeyRound, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Skeleton, Table } from 'antd';
import { Skeleton, Table, Tooltip } from 'antd';
import type { ColumnsType } from 'antd/es/table/interface';
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
@@ -110,28 +110,34 @@ function buildColumns({
onClick: (e): void => e.stopPropagation(),
style: { cursor: 'default' },
}),
render: (_, record): JSX.Element => (
<AuthZTooltip
checks={[
buildAPIKeyDeletePermission(record.id),
buildSADetachPermission(accountId),
]}
enabled={!isDisabled && !!accountId}
>
<Button
variant="ghost"
size="sm"
color="destructive"
disabled={isDisabled}
onClick={(): void => {
onRevokeClick(record.id);
}}
className="keys-tab__revoke-btn"
render: (_, record): JSX.Element => {
const tooltipTitle = isDisabled ? 'Service account disabled' : 'Revoke Key';
return (
<AuthZTooltip
checks={[
buildAPIKeyDeletePermission(record.id),
buildSADetachPermission(accountId),
]}
enabled={!isDisabled && !!accountId}
>
<X size={12} />
</Button>
</AuthZTooltip>
),
<Tooltip title={tooltipTitle}>
<Button
variant="ghost"
size="sm"
color="destructive"
disabled={isDisabled}
onClick={(e): void => {
e.stopPropagation();
onRevokeClick(record.id);
}}
className="keys-tab__revoke-btn"
>
<X size={12} />
</Button>
</Tooltip>
</AuthZTooltip>
);
},
},
];
}

View File

@@ -204,7 +204,7 @@ describe('createGuardedRoute', () => {
).not.toBeInTheDocument();
});
it('should render error fallback when API error occurs', async () => {
it('should render the component when API error occurs (fail open)', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
@@ -230,12 +230,8 @@ describe('createGuardedRoute', () => {
render(<GuardedComponent {...props} />);
await waitFor(() => {
expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument();
expect(screen.getByText('Test Component: test-value')).toBeInTheDocument();
});
expect(
screen.queryByText('Test Component: test-value'),
).not.toBeInTheDocument();
});
it('should render no permissions fallback when permission is denied', async () => {

View File

@@ -9,14 +9,11 @@ import { parsePermission } from 'hooks/useAuthZ/utils';
import noDataUrl from '@/assets/Icons/no-data.svg';
import ErrorBoundaryFallback from '../../pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import AppLoading from '../AppLoading/AppLoading';
import { GuardAuthZ } from '../GuardAuthZ/GuardAuthZ';
import './createGuardedRoute.styles.scss';
const onErrorFallback = (): JSX.Element => <ErrorBoundaryFallback />;
function OnNoPermissionsFallback(response: {
requiredPermissionName: BrandedPermission;
}): ReactElement {
@@ -63,7 +60,6 @@ export function createGuardedRoute<P extends object, R extends AuthZRelation>(
relation={relation}
object={resolvedObject}
fallbackOnLoading={<AppLoading />}
fallbackOnError={onErrorFallback}
fallbackOnNoPermissions={(response): ReactElement => (
<OnNoPermissionsFallback {...response} />
)}

View File

@@ -1,7 +1,8 @@
import { useMemo, useState } from 'react';
import { QueryFunctionContext, useQueries, useQuery } from 'react-query';
import { Spin, Switch, Table, Tooltip } from 'antd';
import { Spin, Table, Tooltip } from 'antd';
import { Info, Loader } from '@signozhq/icons';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import { getQueryRangeV5 } from 'api/v5/queryRange/getQueryRange';
import { MetricRangePayloadV5, ScalarData } from 'api/v5/v5';
@@ -170,11 +171,7 @@ function TopErrors({
/>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Switch
checked={showStatusCodeErrors}
onChange={setShowStatusCodeErrors}
size="small"
/>
<Switch value={showStatusCodeErrors} onChange={setShowStatusCodeErrors} />
<span style={{ color: 'white', fontSize: '14px' }}>
Status Message Exists
</span>

View File

@@ -1,4 +1,5 @@
import { Button, Flex, SelectProps, Switch } from 'antd';
import { Button, Flex, SelectProps } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import type { BaseOptionType, DefaultOptionType } from 'antd/es/select';
import { getInvolvedQueriesInTraceOperator } from 'components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils';
@@ -419,8 +420,8 @@ export function RoutingPolicyBanner({
</Typography.Text>
<div className="routing-policies-info-banner-right">
<Switch
checked={notificationSettings.routingPolicies}
data-testid="routing-policies-switch"
value={notificationSettings.routingPolicies}
testId="routing-policies-switch"
onChange={(value): void => {
setNotificationSettings({
type: 'SET_ROUTING_POLICIES',

View File

@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import { Switch, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import { Info } from '@signozhq/icons';
@@ -49,7 +50,7 @@ function AdvancedOptionItem({
>
{input}
</div>
<Switch onChange={handleOnToggle} checked={showInput} />
<Switch onChange={handleOnToggle} value={showInput} />
</div>
</div>
);

View File

@@ -5,7 +5,8 @@ import { useQuery } from 'react-query';
import { useSelector } from 'react-redux';
import { orange } from '@ant-design/colors';
import { Color } from '@signozhq/design-tokens';
import { Button, Collapse, Input, Select, Switch, Tag } from 'antd';
import { Button, Collapse, Input, Select, Tag } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
import cx from 'classnames';
@@ -763,7 +764,7 @@ function VariableItem({
</Typography>
</LabelContainer>
<Switch
checked={variableMultiSelect}
value={variableMultiSelect}
onChange={(e): void => {
setVariableMultiSelect(e);
if (!e) {
@@ -780,7 +781,7 @@ function VariableItem({
</Typography>
</LabelContainer>
<Switch
checked={variableShowALLOption}
value={variableShowALLOption}
onChange={(e): void => setVariableShowALLOption(e)}
/>
</VariableItemRow>

View File

@@ -0,0 +1,147 @@
import { renderHook } from '@testing-library/react';
import { UseQueryResult } from 'react-query';
import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { usePanelContextMenu } from '../usePanelContextMenu';
// The hook composes `useCoordinates` (popover state) and `useGraphContextMenu`
// (menu items). We mock both so the test focuses on the `enableDrillDown` gate
// rather than the implementation of the menu wiring itself.
const onClickMock = jest.fn();
jest.mock('periscope/components/ContextMenu', () => ({
useCoordinates: (): unknown => ({
coordinates: null,
popoverPosition: null,
clickedData: null,
onClose: jest.fn(),
subMenu: null,
onClick: onClickMock,
setSubMenu: jest.fn(),
}),
}));
jest.mock('container/QueryTable/Drilldown/useGraphContextMenu', () => ({
__esModule: true,
default: (): { menuItemsConfig: { header: string; items: string } } => ({
menuItemsConfig: { header: 'menu-header', items: 'menu-items' },
}),
}));
jest.mock('container/QueryTable/Drilldown/drilldownUtils', () => ({
getUplotClickData: jest.fn(() => ({
coord: { x: 1, y: 2 },
record: { queryName: 'A', filters: [] },
label: 'lbl',
seriesColor: '#abc',
})),
}));
jest.mock('container/PanelWrapper/utils', () => ({
isApmMetric: jest.fn(() => false),
getTimeRangeFromStepInterval: jest.fn(() => ({ start: 0, end: 0 })),
}));
const mockWidget = { id: 'w-1', query: {} } as unknown as Widgets;
const mockQueryResponse = {
data: undefined,
isLoading: false,
} as unknown as UseQueryResult<
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
describe('usePanelContextMenu', () => {
beforeEach(() => {
onClickMock.mockClear();
});
it('returns empty menuItemsConfig when enableDrillDown is false', () => {
const { result } = renderHook(() =>
usePanelContextMenu({
widget: mockWidget,
queryResponse: mockQueryResponse,
enableDrillDown: false,
}),
);
expect(result.current.menuItemsConfig).toStrictEqual({});
});
it('returns wired menuItemsConfig when enableDrillDown is true', () => {
const { result } = renderHook(() =>
usePanelContextMenu({
widget: mockWidget,
queryResponse: mockQueryResponse,
enableDrillDown: true,
}),
);
expect(result.current.menuItemsConfig).toStrictEqual({
header: 'menu-header',
items: 'menu-items',
});
});
it('clickHandlerWithContextMenu is a no-op when enableDrillDown is false', () => {
const { result } = renderHook(() =>
usePanelContextMenu({
widget: mockWidget,
queryResponse: mockQueryResponse,
enableDrillDown: false,
}),
);
result.current.clickHandlerWithContextMenu(
100, // xValue
200, // yValue
0, // mouseX
0, // mouseY
{ serviceName: 'svc' }, // metric
{ queryName: 'A', inFocusOrNot: true }, // queryData
10, // absoluteMouseX
20, // absoluteMouseY
{}, // axesData
{ seriesIndex: 0, seriesName: 'A', value: 1, color: '#abc' }, // focusedSeries
);
expect(onClickMock).not.toHaveBeenCalled();
});
it('clickHandlerWithContextMenu opens popover when enableDrillDown is true', () => {
const { result } = renderHook(() =>
usePanelContextMenu({
widget: mockWidget,
queryResponse: mockQueryResponse,
enableDrillDown: true,
}),
);
result.current.clickHandlerWithContextMenu(
100,
200,
0,
0,
{ serviceName: 'svc' },
{ queryName: 'A', inFocusOrNot: true },
10,
20,
{},
{ seriesIndex: 0, seriesName: 'A', value: 1, color: '#abc' },
);
expect(onClickMock).toHaveBeenCalledTimes(1);
});
it('defaults to disabled when enableDrillDown is not provided', () => {
const { result } = renderHook(() =>
usePanelContextMenu({
widget: mockWidget,
queryResponse: mockQueryResponse,
}),
);
expect(result.current.menuItemsConfig).toStrictEqual({});
});
});

View File

@@ -21,11 +21,13 @@ interface UseTimeSeriesContextMenuParams {
SuccessResponse<MetricRangePayloadProps, unknown>,
Error
>;
enableDrillDown?: boolean;
}
export const usePanelContextMenu = ({
widget,
queryResponse,
enableDrillDown = false,
}: UseTimeSeriesContextMenuParams): {
coordinates: { x: number; y: number } | null;
popoverPosition: PopoverPosition | null;
@@ -61,6 +63,9 @@ export const usePanelContextMenu = ({
const clickHandlerWithContextMenu = useCallback(
(...args: any[]) => {
if (!enableDrillDown) {
return;
}
const [
xValue,
_yvalue,
@@ -112,14 +117,14 @@ export const usePanelContextMenu = ({
});
}
},
[onClick, queryResponse],
[enableDrillDown, onClick, queryResponse],
);
return {
coordinates,
popoverPosition,
onClose,
menuItemsConfig,
menuItemsConfig: enableDrillDown ? menuItemsConfig : {},
clickHandlerWithContextMenu,
};
};

View File

@@ -31,6 +31,7 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
isFullViewMode,
onToggleModelHandler,
groupByPerQuery,
enableDrillDown = false,
} = props;
const uPlotRef = useRef<uPlot | null>(null);
const graphRef = useRef<HTMLDivElement>(null);
@@ -61,6 +62,7 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
} = usePanelContextMenu({
widget,
queryResponse,
enableDrillDown,
});
const config = useMemo(() => {

View File

@@ -31,6 +31,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
isFullViewMode,
onToggleModelHandler,
groupByPerQuery,
enableDrillDown = false,
} = props;
const graphRef = useRef<HTMLDivElement>(null);
const [minTimeScale, setMinTimeScale] = useState<number>();
@@ -60,6 +61,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
} = usePanelContextMenu({
widget,
queryResponse,
enableDrillDown,
});
const chartData = useMemo(() => {

View File

@@ -1,6 +1,7 @@
import { Dispatch, ReactElement, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next';
import { Form, FormInstance, Input, Select, Switch } from 'antd';
import { Form, FormInstance, Input, Select } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import type { Store } from 'antd/lib/form/interface';
import ROUTES from 'constants/routes';
@@ -82,8 +83,8 @@ function FormAlertChannels({
name="send_resolved"
>
<Switch
defaultChecked={initialValue?.send_resolved}
data-testid="field-send-resolved-checkbox"
defaultValue={initialValue?.send_resolved}
testId="field-send-resolved-checkbox"
onChange={(value): void => {
setSelectedConfig((state) => ({
...state,

View File

@@ -2,7 +2,8 @@ import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { Plus } from '@signozhq/icons';
import { Button, Flex, Form, Select, Switch, Tooltip } from 'antd';
import { Button, Flex, Form, Select, Tooltip } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import getAll from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
@@ -198,10 +199,10 @@ function BasicInfo({
placement="right"
>
<Switch
checked={shouldBroadCastToAllChannels}
value={shouldBroadCastToAllChannels}
onChange={handleBroadcastToAllChannels}
disabled={noChannels || !!isLoading}
data-testid="alert-broadcast-to-all-channels"
testId="alert-broadcast-to-all-channels"
/>
</Tooltip>
</FormItemMedium>

View File

@@ -292,6 +292,8 @@ function FullView({
return <Spinner height="100%" size="large" tip="Loading..." />;
}
const showEditBtn = editWidget && dashboardEditView;
return (
<div className="full-view-container">
<OverlayScrollbar>
@@ -306,7 +308,7 @@ function FullView({
Reset Query
</Button>
)}
{editWidget && (
{showEditBtn && (
<Button
className="switch-edit-btn"
disabled={response.isFetching || response.isLoading}

View File

@@ -18,6 +18,8 @@ import listUserPreferences from 'api/v1/user/preferences/list';
import updateUserPreferenceAPI from 'api/v1/user/preferences/name/update';
import Header from 'components/Header/Header';
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
import NoAuthBanner from 'components/NoAuthBanner/NoAuthBanner';
import { getIsNoAuthMode } from 'utils/noAuthMode';
import { ENTITY_VERSION_V5 } from 'constants/app';
import { ORG_PREFERENCES } from 'constants/orgPreferences';
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
@@ -196,7 +198,7 @@ export default function Home(): JSX.Element {
const { mutate: updateUserPreference } = useMutation(updateUserPreferenceAPI, {
onSuccess: () => {
setUpdatingUserPreferences(false);
refetchUserPreferences();
void refetchUserPreferences();
},
onError: () => {
setUpdatingUserPreferences(false);
@@ -204,7 +206,7 @@ export default function Home(): JSX.Element {
});
const handleWillDoThisLater = (): void => {
logEvent('Welcome Checklist: Will do this later clicked', {});
void logEvent('Welcome Checklist: Will do this later clicked', {});
setUpdatingUserPreferences(true);
updateUserPreference({
@@ -271,11 +273,12 @@ export default function Home(): JSX.Element {
}, [metricsOnboardingData, handleUpdateChecklistDoneItem]);
useEffect(() => {
logEvent('Homepage: Visited', {});
void logEvent('Homepage: Visited', {});
}, []);
return (
<div className="home-container">
{getIsNoAuthMode() && <NoAuthBanner />}
<div className="sticky-header">
<Header
leftComponent={
@@ -298,9 +301,9 @@ export default function Home(): JSX.Element {
autoAdjustOverflow
onOpenChange={(visible): void => {
if (visible) {
logEvent('Welcome Checklist: Expanded', {});
void logEvent('Welcome Checklist: Expanded', {});
} else {
logEvent('Welcome Checklist: Minimized', {});
void logEvent('Welcome Checklist: Minimized', {});
}
}}
content={renderWelcomeChecklistModal()}
@@ -353,7 +356,7 @@ export default function Home(): JSX.Element {
className="active-ingestion-card-actions"
onClick={(e: React.MouseEvent): void => {
// eslint-disable-next-line sonarjs/no-duplicate-string
logEvent('Homepage: Ingestion Active Explore clicked', {
void logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Logs',
});
safeNavigate(ROUTES.LOGS_EXPLORER, {
@@ -362,7 +365,7 @@ export default function Home(): JSX.Element {
}}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
logEvent('Homepage: Ingestion Active Explore clicked', {
void logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Logs',
});
history.push(ROUTES.LOGS_EXPLORER);
@@ -396,7 +399,7 @@ export default function Home(): JSX.Element {
role="button"
tabIndex={0}
onClick={(e: React.MouseEvent): void => {
logEvent('Homepage: Ingestion Active Explore clicked', {
void logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Traces',
});
safeNavigate(ROUTES.TRACES_EXPLORER, {
@@ -405,7 +408,7 @@ export default function Home(): JSX.Element {
}}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
logEvent('Homepage: Ingestion Active Explore clicked', {
void logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Traces',
});
history.push(ROUTES.TRACES_EXPLORER);
@@ -439,7 +442,7 @@ export default function Home(): JSX.Element {
role="button"
tabIndex={0}
onClick={(e: React.MouseEvent): void => {
logEvent('Homepage: Ingestion Active Explore clicked', {
void logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Metrics',
});
safeNavigate(ROUTES.METRICS_EXPLORER, {
@@ -448,7 +451,7 @@ export default function Home(): JSX.Element {
}}
onKeyDown={(e): void => {
if (e.key === 'Enter') {
logEvent('Homepage: Ingestion Active Explore clicked', {
void logEvent('Homepage: Ingestion Active Explore clicked', {
source: 'Metrics',
});
history.push(ROUTES.METRICS_EXPLORER);
@@ -496,7 +499,7 @@ export default function Home(): JSX.Element {
className="periscope-btn secondary"
prefix={<Wrench size={14} />}
onClick={(e: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
void logEvent('Homepage: Explore clicked', {
source: 'Logs',
});
safeNavigate(ROUTES.LOGS_EXPLORER, {
@@ -513,7 +516,7 @@ export default function Home(): JSX.Element {
className="periscope-btn secondary"
prefix={<Wrench size={14} />}
onClick={(e: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
void logEvent('Homepage: Explore clicked', {
source: 'Traces',
});
safeNavigate(ROUTES.TRACES_EXPLORER, {
@@ -530,7 +533,7 @@ export default function Home(): JSX.Element {
className="periscope-btn secondary"
prefix={<Wrench size={14} />}
onClick={(e: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
void logEvent('Homepage: Explore clicked', {
source: 'Metrics',
});
safeNavigate(ROUTES.METRICS_EXPLORER_EXPLORER, {
@@ -569,7 +572,7 @@ export default function Home(): JSX.Element {
className="periscope-btn secondary"
prefix={<Plus size={14} />}
onClick={(e: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
void logEvent('Homepage: Explore clicked', {
source: 'Dashboards',
});
safeNavigate(ROUTES.ALL_DASHBOARD, {
@@ -614,7 +617,7 @@ export default function Home(): JSX.Element {
className="periscope-btn secondary"
prefix={<Plus size={14} />}
onClick={(e: React.MouseEvent): void => {
logEvent('Homepage: Explore clicked', {
void logEvent('Homepage: Explore clicked', {
source: 'Alerts',
});
safeNavigate(ROUTES.ALERTS_NEW, {

View File

@@ -15,13 +15,13 @@ import {
Modal,
Row,
Select,
Switch,
Table,
TablePaginationConfig,
TableProps as AntDTableProps,
Tag,
Tooltip,
} from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import type { NotificationInstance } from 'antd/es/notification/interface';
import type { CollapseProps } from 'antd/lib';
@@ -1180,8 +1180,7 @@ function MultiIngestionSettings(): JSX.Element {
<div className="limit-enable-disable-toggle">
<Form.Item name="enableDailyLimit">
<Switch
size="small"
checked={activeSignal?.config?.day?.enabled}
value={activeSignal?.config?.day?.enabled}
onChange={(value): void => {
setActiveSignal((prev) =>
prev
@@ -1270,8 +1269,7 @@ function MultiIngestionSettings(): JSX.Element {
<div className="limit-enable-disable-toggle">
<Form.Item name="enableSecondLimit">
<Switch
size="small"
checked={activeSignal?.config?.second?.enabled}
value={activeSignal?.config?.second?.enabled}
onChange={(value): void => {
setActiveSignal((prev) =>
prev

View File

@@ -21,11 +21,11 @@ import {
Modal,
Popover,
Skeleton,
Switch,
Table,
Tag,
Tooltip,
} from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import type { TableProps } from 'antd/lib';
import getLocalStorageKey from 'api/browser/localstorage/get';
@@ -965,8 +965,7 @@ function DashboardsList(): JSX.Element {
<div className="connection-line" />
<div className="right">
<Switch
size="small"
checked
value
disabled
onChange={(check): void =>
setVisibleColumns((prev) => ({
@@ -985,9 +984,8 @@ function DashboardsList(): JSX.Element {
<div className="connection-line" />
<div className="right">
<Switch
size="small"
disabled
checked
value
onChange={(check): void =>
setVisibleColumns((prev) => ({
...prev,
@@ -1005,8 +1003,7 @@ function DashboardsList(): JSX.Element {
<div className="connection-line" />
<div className="right">
<Switch
size="small"
checked={visibleColumns.updatedAt}
value={visibleColumns.updatedAt}
onChange={(check): void =>
setVisibleColumns((prev) => ({
...prev,
@@ -1024,8 +1021,7 @@ function DashboardsList(): JSX.Element {
<div className="connection-line" />
<div className="right">
<Switch
size="small"
checked={visibleColumns.updatedBy}
value={visibleColumns.updatedBy}
onChange={(check): void =>
setVisibleColumns((prev) => ({
...prev,

View File

@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { Switch } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
import { MAX_LOGS_LIST_SIZE } from 'constants/liveTail';
@@ -228,9 +228,8 @@ function LiveLogsContainer({
<div className="live-logs-frequency-chart-view-controller">
<Typography>Frequency chart</Typography>
<Switch
size="small"
checked={showLiveLogsFrequencyChart}
defaultChecked
value={showLiveLogsFrequencyChart}
defaultValue
onChange={handleToggleFrequencyChart}
/>
</div>

View File

@@ -1,7 +1,7 @@
import { useMemo, useState } from 'react';
import MEditor, { EditorProps, Monaco } from '@monaco-editor/react';
import { Color } from '@signozhq/design-tokens';
import { Switch } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import { useIsDarkMode } from 'hooks/useDarkMode';
@@ -81,7 +81,7 @@ function JSONView({ logData }: JSONViewProps): JSX.Element {
<div className="log-switch">
<div className="wrap-word-switch">
<Typography.Text>Wrap text</Typography.Text>
<Switch checked={isWrapWord} onChange={handleWrapWord} size="small" />
<Switch value={isWrapWord} onChange={handleWrapWord} />
</div>
</div>
</div>

View File

@@ -2,7 +2,8 @@ import { ReactNode, useState } from 'react';
import MEditor, { EditorProps, Monaco } from '@monaco-editor/react';
import { Color } from '@signozhq/design-tokens';
import { Button } from '@signozhq/ui/button';
import { Collapse, Divider, Input, Switch, Tag } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Collapse, Divider, Input, Tag } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
import { ChangeViewFunctionType } from 'container/ExplorerOptions/types';
@@ -136,7 +137,7 @@ function Overview({
<div className="log-switch">
<div className="wrap-word-switch">
<Typography.Text>Wrap text</Typography.Text>
<Switch checked={isWrapWord} onChange={handleWrapWord} size="small" />
<Switch value={isWrapWord} onChange={handleWrapWord} />
</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { Switch } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import DownloadOptionsMenu from 'components/DownloadOptionsMenu/DownloadOptionsMenu';
import LogsFormatOptionsMenu from 'components/LogsFormatOptionsMenu/LogsFormatOptionsMenu';
@@ -69,9 +69,8 @@ function LogsActionsContainer({
<div className="frequency-chart-view-controller">
<Typography>Frequency chart</Typography>
<Switch
size="small"
checked={showFrequencyChart}
defaultChecked
value={showFrequencyChart}
defaultValue
onChange={handleToggleFrequencyChart}
/>
</div>

View File

@@ -21,7 +21,6 @@ const PAGE_SIZE = 20;
function MembersSettings(): JSX.Element {
const history = useHistory();
const urlQuery = useUrlQuery();
const pageParam = parseInt(urlQuery.get('page') ?? '1', 10);
const currentPage = Number.isNaN(pageParam) || pageParam < 1 ? 1 : pageParam;
@@ -146,7 +145,7 @@ function MembersSettings(): JSX.Element {
: `Deleted ⎯ ${deletedCount}`;
const handleInviteComplete = useCallback((): void => {
refetchUsers();
void refetchUsers();
}, [refetchUsers]);
const handleRowClick = useCallback((member: MemberRow): void => {
@@ -158,7 +157,7 @@ function MembersSettings(): JSX.Element {
}, []);
const handleMemberEditComplete = useCallback((): void => {
refetchUsers();
void refetchUsers();
}, [refetchUsers]);
return (

View File

@@ -30,7 +30,12 @@ import { v4 as uuid } from 'uuid';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { GraphTitle, MENU_ITEMS, SERVICE_CHART_ID } from '../constant';
import {
GraphTitle,
MENU_ITEMS,
SERVICE_CHART_ID,
SERVICE_DETAIL_DRILLDOWN_ENABLED,
} from '../constant';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import { Card, GraphContainer, Row } from '../styles';
import { Button } from './styles';
@@ -206,6 +211,7 @@ function DBCall(): JSX.Element {
}}
onDragSelect={onDragSelect}
version={ENTITY_VERSION_V4}
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
/>
</GraphContainer>
</Card>
@@ -244,6 +250,7 @@ function DBCall(): JSX.Element {
}}
onDragSelect={onDragSelect}
version={ENTITY_VERSION_V4}
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
/>
</GraphContainer>
</Card>

View File

@@ -32,7 +32,12 @@ import { v4 as uuid } from 'uuid';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import { GraphTitle, legend, MENU_ITEMS } from '../constant';
import {
GraphTitle,
legend,
MENU_ITEMS,
SERVICE_DETAIL_DRILLDOWN_ENABLED,
} from '../constant';
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
import { Card, GraphContainer, Row } from '../styles';
import GraphControlsPanel from './Overview/GraphControlsPanel/GraphControlsPanel';
@@ -279,6 +284,7 @@ function External(): JSX.Element {
}}
onDragSelect={onDragSelect}
version={ENTITY_VERSION_V4}
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
/>
</GraphContainer>
</Card>
@@ -322,6 +328,7 @@ function External(): JSX.Element {
}}
onDragSelect={onDragSelect}
version={ENTITY_VERSION_V4}
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
/>
</GraphContainer>
</Card>
@@ -366,6 +373,7 @@ function External(): JSX.Element {
}
onDragSelect={onDragSelect}
version={ENTITY_VERSION_V4}
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
/>
</GraphContainer>
</Card>
@@ -409,6 +417,7 @@ function External(): JSX.Element {
}}
onDragSelect={onDragSelect}
version={ENTITY_VERSION_V4}
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
/>
</GraphContainer>
</Card>

View File

@@ -15,6 +15,7 @@ import DisplayThreshold from 'container/GridCardLayout/WidgetHeader/DisplayThres
import {
GraphTitle,
SERVICE_CHART_ID,
SERVICE_DETAIL_DRILLDOWN_ENABLED,
} from 'container/MetricsApplication/constant';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
@@ -105,6 +106,7 @@ function ApDexMetrics({
threshold={threshold}
isQueryEnabled={isQueryEnabled}
version={ENTITY_VERSION_V4}
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
/>
);
}

View File

@@ -8,6 +8,7 @@ import Graph from 'container/GridCardLayout/GridCard';
import {
GraphTitle,
SERVICE_CHART_ID,
SERVICE_DETAIL_DRILLDOWN_ENABLED,
} from 'container/MetricsApplication/constant';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
import { latency } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
@@ -138,6 +139,7 @@ function ServiceOverview({
onClickHandler={handleGraphClick('Service')}
isQueryEnabled={isQueryEnabled}
version={ENTITY_VERSION_V4}
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
/>
)}
</GraphContainer>

View File

@@ -4,6 +4,7 @@ import axios from 'axios';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { ENTITY_VERSION_V4 } from 'constants/app';
import Graph from 'container/GridCardLayout/GridCard';
import { SERVICE_DETAIL_DRILLDOWN_ENABLED } from 'container/MetricsApplication/constant';
import { Card, GraphContainer } from 'container/MetricsApplication/styles';
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
import { Widgets } from 'types/api/dashboard/getAll';
@@ -43,6 +44,7 @@ function TopLevelOperation({
onDragSelect={onDragSelect}
isQueryEnabled={!topLevelOperationsIsLoading}
version={ENTITY_VERSION_V4}
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
/>
)}
</GraphContainer>

View File

@@ -5,11 +5,11 @@ import { useParams } from 'react-router-dom';
import { Search } from '@signozhq/icons';
import {
InputRef,
Switch,
TableColumnsType as ColumnsType,
TableColumnType as ColumnType,
Tooltip,
} from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import { ResizeTable } from 'components/ResizeTable';
import TextToolTip from 'components/TextToolTip';
@@ -212,11 +212,7 @@ function TopOperationsTable({
/>
</div>
<div className="top-operation__entry-point">
<Switch
checked={isEntryPoint}
onChange={onEntryPointToggle}
size="small"
/>
<Switch value={isEntryPoint} onChange={onEntryPointToggle} />
<span className="top-operation__entry-point-label">Entrypoint Spans</span>
<TextToolTip
text={entryPointSpanInfo.text}

View File

@@ -25,6 +25,8 @@ export const OPERATION_LEGENDS = ['Operations'];
export const MENU_ITEMS = [MenuItemKeys.View, MenuItemKeys.CreateAlerts];
export const SERVICE_DETAIL_DRILLDOWN_ENABLED = true;
export enum FORMULA {
ERROR_PERCENTAGE = 'A*100/B',
DATABASE_CALLS_AVG_DURATION = 'A/B',

View File

@@ -2,7 +2,8 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import { useQueryClient } from 'react-query';
import { useSearchParams } from 'react-router-dom-v5-compat';
import * as Sentry from '@sentry/react';
import { Switch, Tooltip } from 'antd';
import { Tooltip } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import logEvent from 'api/common/logEvent';
import { QueryBuilderV2 } from 'components/QueryBuilderV2/QueryBuilderV2';
import WarningPopover from 'components/WarningPopover/WarningPopover';
@@ -358,10 +359,9 @@ function Explorer(): JSX.Element {
title={oneChartPerQueryDisabledTooltip}
>
<Switch
checked={showOneChartPerQuery}
value={showOneChartPerQuery}
onChange={handleToggleShowOneChartPerQuery}
disabled={disableOneChartPerQuery || splitedQueries.length <= 1}
size="small"
/>
</Tooltip>
</div>

View File

@@ -2,7 +2,8 @@ import { useEffect, useMemo, useRef, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { Color } from '@signozhq/design-tokens';
import { Button, Skeleton, Switch } from 'antd';
import { Button, Skeleton } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import Uplot from 'components/Uplot';
@@ -206,7 +207,7 @@ function GraphView({
</Button.Group>
<div className="view-toggle-button">
<Switch
checked={viewType === 'graph'}
value={viewType === 'graph'}
onChange={(checked): void => {
const newViewType = checked ? 'graph' : 'table';
setViewType(newViewType);

View File

@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import { Color } from '@signozhq/design-tokens';
import { Switch } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import logEvent from 'api/common/logEvent';
import { Delete } from '@signozhq/icons';
import { useTimezone } from 'providers/Timezone';
@@ -49,10 +49,10 @@ function TimezoneAdaptation(): JSX.Element {
<div className="timezone-adaption__header">
<h2 className="timezone-adaption__title">Adapt to my timezone</h2>
<Switch
checked={isAdaptationEnabled}
value={isAdaptationEnabled}
onChange={handleSwitchChange}
style={getSwitchStyles()}
data-testid="timezone-adaptation-switch"
testId="timezone-adaptation-switch"
/>
</div>

View File

@@ -127,6 +127,12 @@
flex-direction: column;
gap: 8px;
padding-bottom: 16px;
.password-error-text {
font-size: var(--font-size-xs);
color: var(--bg-cherry-400);
margin-top: 2px;
}
}
.ant-color-picker-trigger {

View File

@@ -1,25 +1,27 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Input, Modal } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import {
updateMyPassword,
useUpdateMyUserV2,
} from 'api/generated/services/users';
import { useNotifications } from 'hooks/useNotifications';
import { toast } from '@signozhq/ui/sonner';
import { Check, FileTerminal, Mail, User } from '@signozhq/icons';
import { useAppContext } from 'providers/App/App';
import { useErrorModal } from 'providers/ErrorModalProvider';
import APIError from 'types/api/error';
import { ErrorV2Resp } from 'types/api';
import { AxiosError } from 'axios';
import '../MySettings.styles.scss';
import './UserInfo.styles.scss';
function UserInfo(): JSX.Element {
const { user, org, updateUser } = useAppContext();
const { t } = useTranslation(['routes', 'settings', 'common']);
const { notifications } = useNotifications();
const { showErrorModal } = useErrorModal();
const { mutateAsync: updateMyUser } = useUpdateMyUserV2();
const [currentPassword, setCurrentPassword] = useState<string>('');
@@ -47,6 +49,8 @@ function UserInfo(): JSX.Element {
const hideResetPasswordModal = (): void => {
setIsResetPasswordModalOpen(false);
setCurrentPassword('');
setUpdatePassword('');
};
const onChangePasswordClickHandler = async (): Promise<void> => {
@@ -57,33 +61,35 @@ function UserInfo(): JSX.Element {
newPassword: updatePassword,
oldPassword: currentPassword,
});
notifications.success({
message: t('success', {
ns: 'common',
}),
});
toast.success('Password updated successfully');
hideResetPasswordModal();
setIsLoading(false);
} catch (error) {
setIsLoading(false);
notifications.error({
message: (error as APIError).error.error.code,
description: (error as APIError).error.error.message,
});
try {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
} catch (apiError) {
showErrorModal(apiError as APIError);
}
}
};
const passwordsMatch =
currentPassword.length > 0 &&
updatePassword.length > 0 &&
currentPassword === updatePassword;
const isResetPasswordDisabled =
isLoading ||
currentPassword.length === 0 ||
updatePassword.length === 0 ||
currentPassword === updatePassword;
passwordsMatch;
const onSaveHandler = async (): Promise<void> => {
logEvent('Account Settings: Name Updated', {
void logEvent('Account Settings: Name Updated', {
name: changedName,
});
logEvent(
void logEvent(
'Account Settings: Name Updated',
{
name: changedName,
@@ -94,11 +100,7 @@ function UserInfo(): JSX.Element {
setIsLoading(true);
await updateMyUser({ data: { displayName: changedName } });
notifications.success({
message: t('success', {
ns: 'common',
}),
});
toast.success('Name updated successfully');
updateUser({
...user,
displayName: changedName,
@@ -106,10 +108,11 @@ function UserInfo(): JSX.Element {
setIsLoading(false);
hideUpdateNameModal();
} catch (error) {
notifications.error({
message: (error as APIError).getErrorCode(),
description: (error as APIError).getErrorMessage(),
});
try {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
} catch (apiError) {
showErrorModal(apiError as APIError);
}
}
setIsLoading(false);
};
@@ -166,7 +169,7 @@ function UserInfo(): JSX.Element {
type="primary"
icon={<Check size={16} />}
onClick={onSaveHandler}
disabled={isLoading}
loading={isLoading}
data-testid="update-name-btn"
>
Update name
@@ -178,7 +181,11 @@ function UserInfo(): JSX.Element {
<Input
placeholder="e.g. John Doe"
value={changedName}
disabled={isLoading}
onChange={(e): void => setChangedName(e.target.value)}
onPressEnter={(): void => {
void onSaveHandler();
}}
/>
</div>
</Modal>
@@ -188,6 +195,7 @@ function UserInfo(): JSX.Element {
title={<span className="title">Reset password</span>}
open={isResetPasswordModalOpen}
closable
destroyOnClose
onCancel={hideResetPasswordModal}
footer={[
<Button
@@ -197,7 +205,8 @@ function UserInfo(): JSX.Element {
}`}
icon={<Check size={16} />}
onClick={onChangePasswordClickHandler}
disabled={isLoading || isResetPasswordDisabled}
loading={isLoading}
disabled={isResetPasswordDisabled}
data-testid="reset-password-btn"
>
Reset password
@@ -218,6 +227,11 @@ function UserInfo(): JSX.Element {
type="password"
autoComplete="off"
visibilityToggle
onPressEnter={(): void => {
if (!isResetPasswordDisabled) {
void onChangePasswordClickHandler();
}
}}
/>
</div>
@@ -235,7 +249,18 @@ function UserInfo(): JSX.Element {
type="password"
autoComplete="off"
visibilityToggle={false}
status={passwordsMatch ? 'error' : ''}
onPressEnter={(): void => {
if (!isResetPasswordDisabled) {
void onChangePasswordClickHandler();
}
}}
/>
{passwordsMatch && (
<span className="password-error-text">
New password must be different from current password
</span>
)}
</div>
</div>
</Modal>

View File

@@ -8,11 +8,23 @@ import {
waitFor,
within,
} from 'tests/test-utils';
import APIError from 'types/api/error';
import { toast } from '@signozhq/ui/sonner';
const toggleThemeFunction = jest.fn();
const logEventFunction = jest.fn();
const copyToClipboardFn = jest.fn();
const editUserFn = jest.fn();
const updateMyPasswordFn = jest.fn();
const showErrorModalFn = jest.fn();
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: {
success: jest.fn(),
error: jest.fn(),
},
}));
jest.mock('react-use', () => ({
__esModule: true,
@@ -24,12 +36,21 @@ jest.mock('react-use', () => ({
jest.mock('api/generated/services/users', () => ({
...jest.requireActual('api/generated/services/users'),
updateMyPassword: (...args: unknown[]): Promise<unknown> =>
updateMyPasswordFn(...args),
useUpdateMyUserV2: jest.fn(() => ({
mutateAsync: (...args: unknown[]): Promise<unknown> => editUserFn(...args),
isLoading: false,
})),
}));
jest.mock('providers/ErrorModalProvider', () => ({
...jest.requireActual('providers/ErrorModalProvider'),
useErrorModal: jest.fn(() => ({
showErrorModal: showErrorModalFn,
})),
}));
jest.mock('hooks/useDarkMode', () => ({
__esModule: true,
useIsDarkMode: jest.fn(() => true),
@@ -65,12 +86,12 @@ const NEW_PASSWORD_TEST_ID = 'new-password-textbox';
const UPDATE_NAME_BUTTON_TEST_ID = 'update-name-btn';
const RESET_PASSWORD_BUTTON_TEST_ID = 'reset-password-btn';
const UPDATE_NAME_BUTTON_TEXT = 'Update name';
const PASSWORD_VALIDATION_MESSAGE_TEST_ID = 'password-validation-message';
describe('MySettings Flows', () => {
beforeEach(() => {
jest.clearAllMocks();
editUserFn.mockResolvedValue({});
updateMyPasswordFn.mockResolvedValue({});
render(<MySettingsContainer />);
});
@@ -152,9 +173,7 @@ describe('MySettings Flows', () => {
fireEvent.click(modalUpdateNameButton);
await waitFor(() =>
expect(successNotification).toHaveBeenCalledWith({
message: 'success',
}),
expect(toast.success).toHaveBeenCalledWith('Name updated successfully'),
);
});
});
@@ -181,22 +200,131 @@ describe('MySettings Flows', () => {
expect(screen.getByTestId(NEW_PASSWORD_TEST_ID)).toBeInTheDocument();
});
it('Should display validation error if password is less than 8 characters', async () => {
it('Should show inline error when new password matches current password', async () => {
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
fireEvent.click(resetPasswordButtons[0]);
const currentPasswordTextbox = screen.getByTestId(CURRENT_PASSWORD_TEST_ID);
act(() => {
fireEvent.change(currentPasswordTextbox, { target: { value: '123' } });
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
target: { value: 'samePassword1' },
});
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
target: { value: 'samePassword1' },
});
});
expect(
screen.getByText('New password must be different from current password'),
).toBeInTheDocument();
expect(screen.getByTestId(RESET_PASSWORD_BUTTON_TEST_ID)).toBeDisabled();
});
it('Should hide inline error when passwords are changed to be different', async () => {
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
fireEvent.click(resetPasswordButtons[0]);
act(() => {
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
target: { value: 'samePassword1' },
});
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
target: { value: 'samePassword1' },
});
});
act(() => {
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
target: { value: 'differentPassword1' },
});
});
expect(
screen.queryByText('New password must be different from current password'),
).not.toBeInTheDocument();
});
it('Should show error modal when password reset API returns an error', async () => {
updateMyPasswordFn.mockRejectedValue(
new Error('Current password is incorrect'),
);
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
fireEvent.click(resetPasswordButtons[0]);
act(() => {
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
target: { value: 'oldPassword1' },
});
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
target: { value: 'newPassword1' },
});
});
fireEvent.click(screen.getByTestId(RESET_PASSWORD_BUTTON_TEST_ID));
await waitFor(() => {
// Use getByTestId for the validation message (if present in your modal/component)
if (screen.queryByTestId(PASSWORD_VALIDATION_MESSAGE_TEST_ID)) {
expect(
screen.getByTestId(PASSWORD_VALIDATION_MESSAGE_TEST_ID),
).toBeInTheDocument();
}
expect(showErrorModalFn).toHaveBeenCalledWith(expect.any(APIError));
});
});
it('Should show success toast and close modal on successful password reset', async () => {
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
fireEvent.click(resetPasswordButtons[0]);
act(() => {
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
target: { value: 'oldPassword1' },
});
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
target: { value: 'newPassword1' },
});
});
fireEvent.click(screen.getByTestId(RESET_PASSWORD_BUTTON_TEST_ID));
await waitFor(() => {
expect(toast.success).toHaveBeenCalledWith('Password updated successfully');
expect(
screen.queryByTestId(CURRENT_PASSWORD_TEST_ID),
).not.toBeInTheDocument();
});
});
it('Should clear password fields when modal is cancelled', async () => {
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
fireEvent.click(resetPasswordButtons[0]);
act(() => {
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
target: { value: 'somePassword' },
});
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
target: { value: 'otherPassword' },
});
});
expect(screen.getByTestId(CURRENT_PASSWORD_TEST_ID)).toHaveValue(
'somePassword',
);
// Close the modal
const closeButton = document.querySelector(
'.reset-password-modal .ant-modal-close',
) as HTMLElement;
fireEvent.click(closeButton);
// Reopen the modal
await waitFor(() => {
expect(
screen.queryByTestId(CURRENT_PASSWORD_TEST_ID),
).not.toBeInTheDocument();
});
fireEvent.click(screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT)[0]);
await waitFor(() => {
expect(screen.getByTestId(CURRENT_PASSWORD_TEST_ID)).toHaveValue('');
expect(screen.getByTestId(NEW_PASSWORD_TEST_ID)).toHaveValue('');
});
});

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { useMutation } from 'react-query';
import { Radio, RadioChangeEvent, Switch, Tag } from 'antd';
import { Radio, RadioChangeEvent, Tag } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import updateUserPreference from 'api/v1/user/preferences/name/update';
@@ -218,10 +219,10 @@ function MySettings(): JSX.Element {
<div className="user-preference-section-content-item-title-action">
Keep the primary sidebar always open{' '}
<Switch
checked={sideNavPinned}
value={sideNavPinned}
onChange={handleSideNavPinnedChange}
loading={isUpdatingUserPreference}
data-testid="side-nav-pinned-switch"
disabled={isUpdatingUserPreference}
testId="side-nav-pinned-switch"
/>
</div>

View File

@@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from 'react';
import { Switch } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import {
FillMode,
@@ -73,7 +73,7 @@ export default function ChartAppearanceSection({
Display individual data points on the chart
</Typography.Text>
</div>
<Switch size="small" checked={showPoints} onChange={setShowPoints} />
<Switch value={showPoints} onChange={setShowPoints} />
</section>
)}
{allowSpanGaps && (

View File

@@ -1,5 +1,6 @@
import { Dispatch, SetStateAction } from 'react';
import { InputNumber, Switch } from 'antd';
import { InputNumber } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import SettingsSection from '../../components/SettingsSection/SettingsSection';
@@ -61,8 +62,7 @@ export default function HistogramBucketsSection({
</span>
</Typography.Text>
<Switch
checked={combineHistogram}
size="small"
value={combineHistogram}
onChange={(checked): void => setCombineHistogram(checked)}
/>
</section>

View File

@@ -1,5 +1,6 @@
import { Dispatch, SetStateAction, useEffect, useState } from 'react';
import { Select, Switch } from 'antd';
import { Select } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Typography } from '@signozhq/ui/typography';
import TimePreference from 'components/TimePreferenceDropDown';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -104,8 +105,7 @@ export default function VisualizationSettingsSection({
<section className="stack-chart control-container">
<Typography.Text className="section-heading">Stack series</Typography.Text>
<Switch
checked={stackedBarChart}
size="small"
value={stackedBarChart}
onChange={(checked): void => setStackedBarChart(checked)}
/>
</section>
@@ -120,8 +120,7 @@ export default function VisualizationSettingsSection({
</Typography.Text>
</div>
<Switch
checked={isFillSpans}
size="small"
value={isFillSpans}
onChange={(checked): void => setIsFillSpans(checked)}
/>
</section>

View File

@@ -341,10 +341,10 @@ describe('Stacking bar in new panel', () => {
const section = container.querySelector('.stack-chart');
expect(section).toBeInTheDocument();
// Verify switch is present and enabled (ant-switch-checked)
const switchBtn = section?.querySelector('.ant-switch');
// Verify switch is present and enabled
const switchBtn = section?.querySelector('[role="switch"]');
expect(switchBtn).toBeInTheDocument();
expect(switchBtn).toHaveClass('ant-switch-checked');
expect(switchBtn).toHaveAttribute('data-state', 'checked');
// Check that stack series is present and checked
checkStackSeriesState(container, true);

View File

@@ -209,7 +209,7 @@ function OnboardingAddDataSource(): JSX.Element {
};
useEffect(() => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.STARTED}`,
{},
);
@@ -253,7 +253,7 @@ function OnboardingAddDataSource(): JSX.Element {
setSelectedFramework(null);
setSelectedEnvironment(null);
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_SELECTED}`,
{
dataSource: dataSource.label,
@@ -276,7 +276,7 @@ function OnboardingAddDataSource(): JSX.Element {
};
const handleSelectFramework = (option: any): void => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.FRAMEWORK_SELECTED}`,
{
dataSource: selectedDataSource?.label,
@@ -309,7 +309,7 @@ function OnboardingAddDataSource(): JSX.Element {
selectedEnvironment: any,
baseURL?: string,
): void => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.ENVIRONMENT_SELECTED}`,
{
dataSource: selectedDataSource?.label,
@@ -351,7 +351,7 @@ function OnboardingAddDataSource(): JSX.Element {
groupDataSourcesByTags(filteredDataSources as Entity[]),
);
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_SEARCHED}`,
{
searchedDataSource: query,
@@ -485,7 +485,7 @@ function OnboardingAddDataSource(): JSX.Element {
};
const handleShowInviteTeamMembersModal = (): void => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INVITE_TEAM_MEMBER_BUTTON_CLICKED}`,
{
dataSource: selectedDataSource?.label,
@@ -498,7 +498,7 @@ function OnboardingAddDataSource(): JSX.Element {
};
const handleSubmitDataSourceRequest = (): void => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_REQUESTED}`,
{
requestedDataSource: dataSourceRequest,
@@ -513,7 +513,7 @@ function OnboardingAddDataSource(): JSX.Element {
};
const handleRaiseRequest = (): void => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_REQUESTED}`,
{
requestedDataSource: searchQuery,
@@ -635,7 +635,7 @@ function OnboardingAddDataSource(): JSX.Element {
size={14}
className="onboarding-header-container-close-icon"
onClick={(e): void => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.CLOSE_ONBOARDING_CLICKED}`,
{
currentPage: setupStepItems[currentStep]?.title || '',
@@ -970,7 +970,7 @@ function OnboardingAddDataSource(): JSX.Element {
disabled={!selectedDataSource}
shape="round"
onClick={(e): void => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.CONFIGURED_PRODUCT}`,
{
dataSource: selectedDataSource?.label,
@@ -1038,7 +1038,7 @@ function OnboardingAddDataSource(): JSX.Element {
type="default"
shape="round"
onClick={(): void => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BACK_BUTTON_CLICKED}`,
{
dataSource: selectedDataSource?.label,
@@ -1057,7 +1057,7 @@ function OnboardingAddDataSource(): JSX.Element {
type="primary"
shape="round"
onClick={(e): void => {
logEvent(
void logEvent(
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.CONTINUE_BUTTON_CLICKED}`,
{
dataSource: selectedDataSource?.label,

View File

@@ -58,6 +58,26 @@ describe('AuthDomain', () => {
});
});
it('reflects ssoEnabled state from nested config in each row toggle', async () => {
server.use(
rest.get(AUTH_DOMAINS_LIST_ENDPOINT, (_, res, ctx) =>
res(ctx.status(200), ctx.json(mockDomainsListResponse)),
),
);
render(<AuthDomain />);
// mockDomainsListResponse rows:
// [0] signoz.io → config.ssoEnabled: true
// [1] example.com → config.ssoEnabled: false
// [2] corp.io → config.ssoEnabled: true
const switches = await screen.findAllByRole('switch');
expect(switches).toHaveLength(3);
expect(switches[0]).toBeChecked();
expect(switches[1]).not.toBeChecked();
expect(switches[2]).toBeChecked();
});
it('renders empty state when no domains exist', async () => {
server.use(
rest.get(AUTH_DOMAINS_LIST_ENDPOINT, (_, res, ctx) =>

View File

@@ -75,7 +75,7 @@ function AuthDomain(): JSX.Element {
{
onSuccess: () => {
toast.success('Domain deleted successfully');
refetchAuthDomainListResponse();
void refetchAuthDomainListResponse();
hideDeleteModal();
},
onError: (error) => {
@@ -121,14 +121,14 @@ function AuthDomain(): JSX.Element {
},
{
title: 'Enforce SSO',
dataIndex: 'ssoEnabled',
dataIndex: ['config', 'ssoEnabled'],
key: 'ssoEnabled',
width: 80,
render: (
value: boolean,
record: AuthtypesGettableAuthDomainDTO,
): JSX.Element => (
<SSOEnforcementToggle isDefaultChecked={value} record={record} />
<SSOEnforcementToggle isDefaultChecked={!!value} record={record} />
),
},
{

View File

@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import { Info } from '@signozhq/icons';
import { Flex, Form, Input, Space, Switch, Tooltip } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { Flex, Form, Input, Space, Tooltip } from 'antd';
import { ProcessorData } from 'types/api/pipeline/def';
import { PREDEFINED_MAPPING } from '../config';
@@ -58,11 +59,7 @@ function JsonFlattening({
initialValue={isAdd ? true : selectedProcessorData?.enable_paths}
>
<Space>
<Switch
size="small"
checked={enablePaths}
onChange={handleEnablePathsChange}
/>
<Switch value={enablePaths} onChange={handleEnablePathsChange} />
Enable Paths
</Space>
</Form.Item>
@@ -79,11 +76,7 @@ function JsonFlattening({
<Form.Item className="json-flattening-form__item">
<Space>
<Switch
size="small"
checked={enableMapping}
onChange={handleEnableMappingChange}
/>
<Switch value={enableMapping} onChange={handleEnableMappingChange} />
<Flex gap="8px" align="center">
Enable Mapping
<Tooltip title="The order of filled keys will determine the priority of keys i.e. earlier keys have higher precedence">

View File

@@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next';
import { Form, Input, Select, Space, Switch } from 'antd';
import { Form, Input, Select, Space } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { ModalFooterTitle } from 'container/PipelinePage/styles';
import { ProcessorData } from 'types/api/pipeline/def';
@@ -92,8 +93,7 @@ function ProcessorFieldInput({
>
<Space>
<Switch
size="small"
checked={form.getFieldValue('enable_flattening')}
value={form.getFieldValue('enable_flattening')}
onChange={(checked: boolean): void => {
form.setFieldValue('enable_flattening', checked);
}}

View File

@@ -1,5 +1,5 @@
import { GripVertical } from '@signozhq/icons';
import { Switch } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { holdIconStyle } from '../config';
import { LastActionColumn } from '../styles';
@@ -7,7 +7,7 @@ import { LastActionColumn } from '../styles';
function DragAction({ isEnabled, onChange }: DragActionProps): JSX.Element {
return (
<LastActionColumn>
<Switch defaultChecked={isEnabled} onChange={onChange} />
<Switch defaultValue={isEnabled} onChange={onChange} />
<GripVertical size="lg" style={holdIconStyle} />
</LastActionColumn>
);

View File

@@ -191,7 +191,7 @@ describe('PipelinePage container test', () => {
await fireEvent.click(expandIcon[0]);
const switchToggle = document.querySelector(
'.ant-table-expanded-row .ant-switch',
'.ant-table-expanded-row [role="switch"]',
);
expect(switchToggle).toBeChecked();
@@ -244,7 +244,7 @@ describe('PipelinePage container test', () => {
const addNewPipelineBtn = getByText('add_new_pipeline');
expect(addNewPipelineBtn).toBeInTheDocument();
const switchToggle = document.querySelectorAll('.ant-switch');
const switchToggle = document.querySelectorAll('[role="switch"]');
expect(switchToggle[0]).not.toBeChecked();
await fireEvent.click(switchToggle[0] as HTMLElement);

View File

@@ -21,26 +21,25 @@ exports[`PipelinePage container test should render DragAction section 1`] = `
<div
class="c0"
>
<button
aria-checked="true"
class="ant-switch css-dev-only-do-not-override-2i2tap ant-switch-checked"
role="switch"
type="button"
<div
class="_switch-wrapper_jbsv7_1"
>
<div
class="ant-switch-handle"
/>
<span
class="ant-switch-inner"
<button
aria-checked="true"
class="_switch_jbsv7_1"
data-color="robin"
data-state="checked"
id=":r0:"
role="switch"
type="button"
value="on"
>
<span
class="ant-switch-inner-checked"
class="_switch__thumb_jbsv7_59"
data-state="checked"
/>
<span
class="ant-switch-inner-unchecked"
/>
</span>
</button>
</button>
</div>
<svg
style="font-size: 1rem; cursor: move;"
/>

View File

@@ -169,9 +169,10 @@ describe('drilldownUtils', () => {
// Verify transformations were applied
if (filterExpression) {
// Rule 2: operation → name
expect(filterExpression).toContain(`name = 'GET'`);
// `operation` rewrites to `name` via source-side pass, then `name`
// is dropped by the logs target-side pass (logs has no span-name).
expect(filterExpression).not.toContain(`operation = 'GET'`);
expect(filterExpression).not.toContain(`name = 'GET'`);
// Rule 3: span.kind → kind
expect(filterExpression).toContain(`${spanKindKey} = '${spanKindServer}'`);
@@ -262,8 +263,9 @@ describe('drilldownUtils', () => {
const filterExpression = result?.builder.queryData[0]?.filter?.expression;
if (filterExpression) {
// All transformations should be applied
expect(filterExpression).toContain(`name = 'POST'`);
// `operation` rewrites to `name` then drops for logs target.
expect(filterExpression).not.toContain(`operation = 'POST'`);
expect(filterExpression).not.toContain(`name = 'POST'`);
expect(filterExpression).toContain(`${spanKindKey} = '${spanKindClient}'`);
expect(filterExpression).toContain(`status_code_string = 'Error'`);
expect(filterExpression).toContain(`http.status_code = 500`);
@@ -410,8 +412,9 @@ describe('drilldownUtils', () => {
const filterExpression = result?.builder.queryData[0]?.filter?.expression;
if (filterExpression) {
// Transformed attributes
expect(filterExpression).toContain(`name = 'GET'`);
// `operation` rewrites to `name` then drops for logs target.
expect(filterExpression).not.toContain(`operation = 'GET'`);
expect(filterExpression).not.toContain(`name = 'GET'`);
expect(filterExpression).toContain(`${spanKindKey} = '${spanKindServer}'`);
// Preserved non-metric attributes
@@ -499,4 +502,189 @@ describe('drilldownUtils', () => {
});
});
});
describe('getViewQuery target-aware sanitisation (serviceName / name)', () => {
const makeQuery = (
expression: string,
dataSource: 'traces' | 'logs' | 'metrics' = 'traces',
): Query => ({
id: 'src-query',
queryType: 'builder' as any,
builder: {
queryData: [
{
queryName: 'src',
dataSource: dataSource as any,
aggregations: [{ metricName: 'non_apm_metric' }] as any,
groupBy: [],
expression: '',
disabled: false,
functions: [],
legend: '',
having: [],
limit: null,
stepInterval: undefined,
orderBy: [],
filter: { expression },
},
],
queryFormulas: [],
queryTraceOperator: [],
},
promql: [],
clickhouse_sql: [],
});
it('rewrites serviceName -> service.name when drilling to logs', () => {
const result = getViewQuery(
makeQuery(`serviceName = 'svc'`),
[],
'view_logs',
'src',
);
const expr = result?.builder.queryData[0]?.filter?.expression || '';
expect(expr).toContain(`service.name = 'svc'`);
expect(expr).not.toContain('serviceName');
});
it('rewrites serviceName -> service.name when drilling to traces', () => {
const result = getViewQuery(
makeQuery(`serviceName = 'svc'`),
[],
'view_traces',
'src',
);
const expr = result?.builder.queryData[0]?.filter?.expression || '';
expect(expr).toContain(`service.name = 'svc'`);
expect(expr).not.toContain('serviceName');
});
it('drops `name` clause when drilling to logs', () => {
const result = getViewQuery(
makeQuery(`name = 'GET /api'`),
[],
'view_logs',
'src',
);
const expr = result?.builder.queryData[0]?.filter?.expression || '';
expect(expr).not.toContain(`name = 'GET /api'`);
});
it('keeps `name` clause when drilling to traces', () => {
const result = getViewQuery(
makeQuery(`name = 'GET /api'`),
[],
'view_traces',
'src',
);
const expr = result?.builder.queryData[0]?.filter?.expression || '';
expect(expr).toContain(`name = 'GET /api'`);
});
it('combined: drilling to logs rewrites serviceName and drops name', () => {
const result = getViewQuery(
makeQuery(`serviceName = 'svc' AND name = 'GET /api'`),
[],
'view_logs',
'src',
);
const expr = result?.builder.queryData[0]?.filter?.expression || '';
expect(expr).toContain(`service.name = 'svc'`);
expect(expr).not.toContain('serviceName');
expect(expr).not.toContain(`name = 'GET /api'`);
});
it('combined: drilling to traces rewrites serviceName and keeps name', () => {
const result = getViewQuery(
makeQuery(`serviceName = 'svc' AND name = 'GET /api'`),
[],
'view_traces',
'src',
);
const expr = result?.builder.queryData[0]?.filter?.expression || '';
expect(expr).toContain(`service.name = 'svc'`);
expect(expr).toContain(`name = 'GET /api'`);
expect(expr).not.toContain('serviceName');
});
it('metric-APM source -> traces target preserves existing operation -> name rewrite', () => {
const metricsQuery: Query = {
id: 'apm-metrics',
queryType: 'builder' as any,
builder: {
queryData: [
{
queryName: 'm',
dataSource: 'metrics' as any,
aggregations: [{ metricName: 'signoz_calls_total' }] as any,
groupBy: [],
expression: '',
disabled: false,
functions: [],
legend: '',
having: [],
limit: null,
stepInterval: undefined,
orderBy: [],
filter: { expression: `operation = 'GET'` },
},
],
queryFormulas: [],
queryTraceOperator: [],
},
promql: [],
clickhouse_sql: [],
};
const result = getViewQuery(metricsQuery, [], 'view_traces', 'm');
const expr = result?.builder.queryData[0]?.filter?.expression || '';
expect(expr).toContain(`name = 'GET'`);
expect(expr).not.toContain(`operation = 'GET'`);
});
it('metric-APM source -> logs target: operation rewrites to name, then dropped', () => {
const metricsQuery: Query = {
id: 'apm-metrics',
queryType: 'builder' as any,
builder: {
queryData: [
{
queryName: 'm',
dataSource: 'metrics' as any,
aggregations: [{ metricName: 'signoz_calls_total' }] as any,
groupBy: [],
expression: '',
disabled: false,
functions: [],
legend: '',
having: [],
limit: null,
stepInterval: undefined,
orderBy: [],
filter: { expression: `operation = 'GET'` },
},
],
queryFormulas: [],
queryTraceOperator: [],
},
promql: [],
clickhouse_sql: [],
};
const result = getViewQuery(metricsQuery, [], 'view_logs', 'm');
const expr = result?.builder.queryData[0]?.filter?.expression || '';
expect(expr).not.toContain(`operation = 'GET'`);
expect(expr).not.toContain(`name = 'GET'`);
});
it('drilling to metrics does not apply target-side sanitisation', () => {
const result = getViewQuery(
makeQuery(`serviceName = 'svc' AND name = 'GET /api'`),
[],
'view_metrics',
'src',
);
const expr = result?.builder.queryData[0]?.filter?.expression || '';
expect(expr).toContain('serviceName');
expect(expr).toContain(`name = 'GET /api'`);
});
});
});

View File

@@ -7,8 +7,10 @@ import {
import ROUTES from 'constants/routes';
import { isApmMetric } from 'container/PanelWrapper/utils';
import {
applyMappingsToExpression,
DRILLDOWN_TO_LOGS_MAPPINGS,
DRILLDOWN_TO_TRACES_MAPPINGS,
METRIC_TO_LOGS_TRACES_MAPPINGS,
replaceKeysAndValuesInExpression,
} from 'container/QueryTable/Drilldown/metricsCorrelationUtils';
import cloneDeep from 'lodash-es/cloneDeep';
import {
@@ -347,27 +349,41 @@ export const getViewQuery = (
newQuery.builder.queryData[0].filter = newFilterExpression;
try {
// ===========================================
// TEMP LOGIC - TO BE REMOVED LATER
// ===========================================
// Apply metric-to-logs/traces transformations
// Drill-down filter sanitisation. Two stages:
// 1. Source-side: rewrite metric-APM-specific keys (operation, span.kind,
// status.code) so they map onto trace/log columns.
// 2. Target-side: normalise legacy keys to OTel-canonical (`serviceName`
// -> `service.name`) and drop keys with no equivalent in the target
// datasource (e.g. `name` for logs).
let expression = newFilterExpression?.expression || '';
const specificQuery = getQueryData(query, queryName);
const isMetricQuery = specificQuery?.dataSource === 'metrics';
const metricName = (specificQuery?.aggregations?.[0] as MetricAggregation)
?.metricName;
if (isMetricQuery && isApmMetric(metricName || '')) {
const transformedExpression = replaceKeysAndValuesInExpression(
newFilterExpression?.expression || '',
expression = applyMappingsToExpression(
expression,
METRIC_TO_LOGS_TRACES_MAPPINGS,
);
newQuery.builder.queryData[0].filter = {
expression: transformedExpression || '',
};
}
// ===========================================
if (key === 'view_logs') {
expression = applyMappingsToExpression(
expression,
DRILLDOWN_TO_LOGS_MAPPINGS,
);
} else if (key === 'view_traces') {
expression = applyMappingsToExpression(
expression,
DRILLDOWN_TO_TRACES_MAPPINGS,
);
}
newQuery.builder.queryData[0].filter = { expression };
} catch (error) {
console.error('Error transforming metrics to logs/traces:', error);
console.error('Error sanitising drilldown filter expression:', error);
}
return newQuery;

View File

@@ -1,5 +1,8 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
import {
formatValueForExpression,
removeKeysFromExpression,
} from 'components/QueryBuilderV2/utils';
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
import { IQueryPair } from 'types/antlrQueryTypes';
import { extractQueryPairs } from 'utils/queryContextUtils';
@@ -8,7 +11,7 @@ import { isFunctionOperator, isNonValueOperator } from 'utils/tokenUtils';
type KeyValueMapping = {
attribute: string;
newAttribute: string;
newAttribute: string | null;
valueMappings: Record<string, string>;
};
@@ -40,8 +43,33 @@ export const METRIC_TO_LOGS_TRACES_MAPPINGS: KeyValueMapping[] = [
},
];
export const DRILLDOWN_TO_LOGS_MAPPINGS: KeyValueMapping[] = [
{
attribute: 'serviceName',
newAttribute: 'service.name',
valueMappings: {},
},
{
attribute: 'name',
newAttribute: null,
valueMappings: {},
},
];
export const DRILLDOWN_TO_TRACES_MAPPINGS: KeyValueMapping[] = [
{
attribute: 'serviceName',
newAttribute: 'service.name',
valueMappings: {},
},
];
// Logic for rewriting key/values in an expression using provided mappings.
function modifyKeyVal(pair: IQueryPair, mapping: KeyValueMapping): string {
// Callers must pre-filter mappings to ensure newAttribute is non-null.
function modifyKeyVal(
pair: IQueryPair,
mapping: KeyValueMapping & { newAttribute: string },
): string {
const newKey = mapping.newAttribute;
const op = pair.operator;
@@ -107,8 +135,18 @@ export function replaceKeysAndValuesInExpression(
return expression;
}
const attributeToMapping = new Map<string, KeyValueMapping>(
mappingList.map((m) => [m.attribute.trim().toLowerCase(), m]),
// Only rewrite mappings (newAttribute non-null) are processed here.
// Drops are handled separately by applyMappingsToExpression via removeKeysFromExpression.
const attributeToMapping = new Map<
string,
KeyValueMapping & { newAttribute: string }
>(
mappingList
.filter(
(m): m is KeyValueMapping & { newAttribute: string } =>
m.newAttribute !== null,
)
.map((m) => [m.attribute.trim().toLowerCase(), m]),
);
const pairs: IQueryPair[] = extractQueryPairs(expression);
@@ -179,3 +217,26 @@ export function replaceKeysAndValuesInExpression(
return resultParts.join('');
}
// Apply a list of mappings to a filter expression. Rewrites are applied first
// (newAttribute is a string), then drops (newAttribute is null) via the
// ANTLR-parser-based removeKeysFromExpression which handles AND/OR/NOT/paren
// elision correctly.
export function applyMappingsToExpression(
expression: string,
mappings: KeyValueMapping[],
): string {
if (!expression || !mappings || mappings.length === 0) {
return expression;
}
const dropKeys = mappings
.filter((m) => m.newAttribute === null)
.map((m) => m.attribute);
let result = replaceKeysAndValuesInExpression(expression, mappings);
if (dropKeys.length > 0) {
result = removeKeysFromExpression(result, dropKeys);
}
return result;
}

View File

@@ -101,6 +101,7 @@ export function getAppContextMockState(
userPreferences: null,
hostsData: null,
isLoggedIn: false,
isPreflightLoading: false,
org: null,
isFetchingUser: false,
isFetchingActiveLicense: false,

View File

@@ -1120,6 +1120,7 @@
.user-settings-dropdown-logout-section {
color: var(--danger-background);
pointer-events: auto;
}
}
}

View File

@@ -408,7 +408,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
);
const handleReorderShortcutNavItems = (): void => {
logEvent('Sidebar V2: Save shortcuts clicked', {
void logEvent('Sidebar V2: Save shortcuts clicked', {
shortcuts: tempPinnedMenuItems.map((item) => item.key),
});
setPinnedMenuItems(tempPinnedMenuItems);
@@ -436,7 +436,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
const isWorkspaceBlocked = trialInfo?.workSpaceBlock || false;
const onClickGetStarted = (event: MouseEvent): void => {
logEvent('Sidebar: Menu clicked', {
void logEvent('Sidebar: Menu clicked', {
menuRoute: '/get-started',
menuLabel: 'Get Started',
});
@@ -651,7 +651,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
} else if (item) {
onClickHandler(item?.key as string, event);
}
logEvent('Sidebar V2: Menu clicked', {
void logEvent('Sidebar V2: Menu clicked', {
menuRoute: item?.key,
menuLabel: item?.label,
});
@@ -794,7 +794,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
onTogglePin={
allowPin
? (item): void => {
logEvent(
void logEvent(
`Sidebar V2: Menu item ${item.isPinned ? 'unpinned' : 'pinned'}`,
{
menuRoute: item.key,
@@ -841,7 +841,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
const event = (info as SidebarItem & { domEvent?: MouseEvent }).domEvent;
if (item && !('type' in item)) {
logEvent('Help Popover: Item clicked', {
void logEvent('Help Popover: Item clicked', {
menuRoute: item.key,
menuLabel: String(item.label),
});
@@ -890,7 +890,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
menuLabel = item.label;
}
logEvent('Settings Popover: Item clicked', {
void logEvent('Settings Popover: Item clicked', {
menuRoute: item?.key,
menuLabel,
});
@@ -927,7 +927,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
}
break;
case 'logout':
Logout();
void Logout();
break;
default:
}
@@ -1081,7 +1081,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
<div
className="nav-section-title-icon reorder"
onClick={(): void => {
logEvent('Sidebar V2: Manage shortcuts clicked', {});
void logEvent('Sidebar V2: Manage shortcuts clicked', {});
setIsReorderShortcutNavItemsModalOpen(true);
}}
>
@@ -1128,7 +1128,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
return;
}
const newCollapsedState = !isMoreMenuCollapsed;
logEvent('Sidebar V2: More menu clicked', {
void logEvent('Sidebar V2: More menu clicked', {
action: isMoreMenuCollapsed ? 'expand' : 'collapse',
});
setIsMoreMenuCollapsed(newCollapsedState);
@@ -1234,14 +1234,14 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
open={isReorderShortcutNavItemsModalOpen}
closable
onCancel={(): void => {
logEvent('Sidebar V2: Manage shortcuts dismissed', {});
void logEvent('Sidebar V2: Manage shortcuts dismissed', {});
hideReorderShortcutNavItemsModal();
}}
footer={[
<Button
key="cancel"
onClick={(): void => {
logEvent('Sidebar V2: Manage shortcuts dismissed', {});
void logEvent('Sidebar V2: Manage shortcuts dismissed', {});
hideReorderShortcutNavItemsModal();
}}
className="periscope-btn cancel-btn secondary-btn"

View File

@@ -1,3 +1,5 @@
import { MenuProps } from 'antd';
import ROUTES from 'constants/routes';
import {
ArrowUpRight,
BarChart,
@@ -35,15 +37,13 @@ import {
Users,
Binoculars,
} from '@signozhq/icons';
import { Style } from '@signozhq/design-tokens';
import { MenuProps } from 'antd';
import ROUTES from 'constants/routes';
import {
SecondaryMenuItemKey,
SettingsNavSection,
SidebarItem,
} from './sideNav.types';
import { Style } from '@signozhq/design-tokens';
export const getStartedMenuItem = {
key: ROUTES.GET_STARTED,

View File

@@ -48,11 +48,10 @@ describe('useAuthZ', () => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.error).toBeNull();
expect(result.current.permissions).toStrictEqual(expectedResponse);
});
it('should handle API errors', async () => {
it('should return error and null permissions when API errors', async () => {
const permission = buildPermission('read', 'role:*');
server.use(
@@ -191,7 +190,6 @@ describe('useAuthZ', () => {
});
expect(result.current.isLoading).toBe(false);
expect(result.current.error).toBeNull();
expect(result.current.permissions).toStrictEqual({});
});

View File

@@ -1,6 +1,7 @@
import { useCallback, useEffect, useState } from 'react';
import { Color } from '@signozhq/design-tokens';
import { Divider, Dropdown, MenuProps, Switch, Tooltip } from 'antd';
import { Divider, Dropdown, MenuProps, Tooltip } from 'antd';
import { Switch } from '@signozhq/ui/switch';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { Copy, Ellipsis, PenLine, Trash2 } from '@signozhq/icons';
import {
@@ -132,11 +133,7 @@ function AlertActionButtons({
<div className="alert-action-buttons">
<Tooltip title={isAlertRuleDisabled ? 'Enable alert' : 'Disable alert'}>
{isAlertRuleDisabled !== undefined && (
<Switch
size="small"
onChange={toggleAlertRule}
checked={!isAlertRuleDisabled}
/>
<Switch onChange={toggleAlertRule} value={!isAlertRuleDisabled} />
)}
</Tooltip>
<CopyToClipboard textToCopy={window.location.href} />

View File

@@ -321,7 +321,7 @@ function SettingsPage(): JSX.Element {
isDisabled={false}
showIcon={false}
onClick={(event): void => {
logEvent('Settings V2: Menu clicked', {
void logEvent('Settings V2: Menu clicked', {
menuLabel: item.label,
menuRoute: item.key,
});

View File

@@ -119,6 +119,12 @@
flex-shrink: 0;
}
.statusMessageBadge {
width: 100%;
min-width: 0;
box-sizing: border-box;
}
.traceId {
color: var(--accent-primary);
overflow: hidden;

View File

@@ -1,5 +1,6 @@
import { ReactNode } from 'react';
import { Badge } from '@signozhq/ui/badge';
import ExpandableValue from 'periscope/components/ExpandableValue';
import { SpanV3 } from 'types/api/trace/getTraceV3';
import styles from './SpanDetailsPanel.module.scss';
@@ -48,7 +49,15 @@ export const HIGHLIGHTED_OPTIONS: HighlightedOption[] = [
label: 'STATUS MESSAGE',
render: (span): ReactNode | null =>
span.status_message ? (
<Badge color="vanilla">{span.status_message}</Badge>
<ExpandableValue value={span.status_message} title="Status message">
<Badge
color="vanilla"
textEllipsis="end"
className={styles.statusMessageBadge}
>
{span.status_message}
</Badge>
</ExpandableValue>
) : null,
},
];

View File

@@ -0,0 +1,3 @@
.traceOptionsDropdown {
z-index: 1100;
}

View File

@@ -6,6 +6,8 @@ import { Ellipsis } from '@signozhq/icons';
import { useTraceStore } from '../stores/traceStore';
import styles from './TraceOptionsMenu.module.scss';
interface TraceOptionsMenuProps {
showTraceDetails: boolean;
onToggleTraceDetails: () => void;
@@ -82,7 +84,11 @@ function TraceOptionsMenu({
]);
return (
<Dropdown menu={{ items: menuItems }} align="start">
<Dropdown
menu={{ items: menuItems }}
align="start"
className={styles.traceOptionsDropdown}
>
<Button
variant="ghost"
size="icon"

View File

@@ -6,9 +6,9 @@ import {
Form,
MenuProps,
Space,
Switch,
Tooltip,
} from 'antd';
import { Switch } from '@signozhq/ui/switch';
import cx from 'classnames';
import { FilterSelect } from 'components/CeleryOverview/CeleryOverviewConfigOptions/CeleryOverviewConfigOptions';
import { QueryParams } from 'constants/query';
@@ -201,8 +201,7 @@ function FunnelStep({
<div className="error">
<Switch
className="error__switch"
size="small"
checked={stepData.has_errors}
value={stepData.has_errors}
disabled={!hasEditPermission}
onChange={(): void =>
onStepChange(index, { has_errors: !stepData.has_errors })

File diff suppressed because one or more lines are too long

View File

@@ -24,13 +24,12 @@ HASTOKEN=23
HAS=24
HASANY=25
HASALL=26
SEARCH=27
BOOL=28
NUMBER=29
QUOTED_TEXT=30
KEY=31
WS=32
FREETEXT=33
BOOL=27
NUMBER=28
QUOTED_TEXT=29
KEY=30
WS=31
FREETEXT=32
'('=1
')'=2
'['=3

File diff suppressed because one or more lines are too long

View File

@@ -24,13 +24,12 @@ HASTOKEN=23
HAS=24
HASANY=25
HASALL=26
SEARCH=27
BOOL=28
NUMBER=29
QUOTED_TEXT=30
KEY=31
WS=32
FREETEXT=33
BOOL=27
NUMBER=28
QUOTED_TEXT=29
KEY=30
WS=31
FREETEXT=32
'('=1
')'=2
'['=3

View File

@@ -1,4 +1,4 @@
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
// Generated from FilterQuery.g4 by ANTLR 4.13.1
// noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols
import {
ATN,
@@ -38,13 +38,12 @@ export default class FilterQueryLexer extends Lexer {
public static readonly HAS = 24;
public static readonly HASANY = 25;
public static readonly HASALL = 26;
public static readonly SEARCH = 27;
public static readonly BOOL = 28;
public static readonly NUMBER = 29;
public static readonly QUOTED_TEXT = 30;
public static readonly KEY = 31;
public static readonly WS = 32;
public static readonly FREETEXT = 33;
public static readonly BOOL = 27;
public static readonly NUMBER = 28;
public static readonly QUOTED_TEXT = 29;
public static readonly KEY = 30;
public static readonly WS = 31;
public static readonly FREETEXT = 32;
public static readonly EOF = Token.EOF;
public static readonly channelNames: string[] = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ];
@@ -69,9 +68,8 @@ export default class FilterQueryLexer extends Lexer {
"AND", "OR",
"HASTOKEN",
"HAS", "HASANY",
"HASALL", "SEARCH",
"BOOL", "NUMBER",
"QUOTED_TEXT",
"HASALL", "BOOL",
"NUMBER", "QUOTED_TEXT",
"KEY", "WS",
"FREETEXT" ];
public static readonly modeNames: string[] = [ "DEFAULT_MODE", ];
@@ -80,8 +78,8 @@ export default class FilterQueryLexer extends Lexer {
"LPAREN", "RPAREN", "LBRACK", "RBRACK", "COMMA", "EQUALS", "NOT_EQUALS",
"NEQ", "LT", "LE", "GT", "GE", "LIKE", "ILIKE", "BETWEEN", "EXISTS", "REGEXP",
"CONTAINS", "IN", "NOT", "AND", "OR", "HASTOKEN", "HAS", "HASANY", "HASALL",
"SEARCH", "BOOL", "SIGN", "NUMBER", "QUOTED_TEXT", "SEGMENT", "EMPTY_BRACKS",
"OLD_JSON_BRACKS", "KEY", "WS", "DIGIT", "FREETEXT",
"BOOL", "SIGN", "NUMBER", "QUOTED_TEXT", "SEGMENT", "EMPTY_BRACKS", "OLD_JSON_BRACKS",
"KEY", "WS", "DIGIT", "FREETEXT",
];
@@ -102,122 +100,119 @@ export default class FilterQueryLexer extends Lexer {
public get modeNames(): string[] { return FilterQueryLexer.modeNames; }
public static readonly _serializedATN: number[] = [4,0,33,329,6,-1,2,0,
public static readonly _serializedATN: number[] = [4,0,32,320,6,-1,2,0,
7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,
7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,
16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,
2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,
31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,1,0,
1,0,1,1,1,1,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,3,5,91,8,5,1,6,1,6,1,6,
1,7,1,7,1,7,1,8,1,8,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,
1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,
14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,3,15,134,8,15,1,16,1,16,1,16,1,16,
1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,3,17,151,8,17,1,
18,1,18,1,18,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,22,
1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,24,1,24,1,
24,1,24,1,24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,
1,26,1,26,1,26,1,26,1,27,1,27,1,27,1,27,1,27,1,27,1,27,1,27,1,27,3,27,210,
8,27,1,28,1,28,1,29,3,29,215,8,29,1,29,4,29,218,8,29,11,29,12,29,219,1,
29,1,29,5,29,224,8,29,10,29,12,29,227,9,29,3,29,229,8,29,1,29,1,29,3,29,
233,8,29,1,29,4,29,236,8,29,11,29,12,29,237,3,29,240,8,29,1,29,3,29,243,
8,29,1,29,1,29,4,29,247,8,29,11,29,12,29,248,1,29,1,29,3,29,253,8,29,1,
29,4,29,256,8,29,11,29,12,29,257,3,29,260,8,29,3,29,262,8,29,1,30,1,30,
1,30,1,30,5,30,268,8,30,10,30,12,30,271,9,30,1,30,1,30,1,30,1,30,1,30,5,
30,278,8,30,10,30,12,30,281,9,30,1,30,3,30,284,8,30,1,31,1,31,5,31,288,
8,31,10,31,12,31,291,9,31,1,32,1,32,1,32,1,33,1,33,1,33,1,33,1,34,1,34,
1,34,1,34,1,34,1,34,1,34,4,34,307,8,34,11,34,12,34,308,5,34,311,8,34,10,
34,12,34,314,9,34,1,35,4,35,317,8,35,11,35,12,35,318,1,35,1,35,1,36,1,36,
1,37,4,37,326,8,37,11,37,12,37,327,0,0,38,1,1,3,2,5,3,7,4,9,5,11,6,13,7,
15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,16,33,17,35,18,37,19,39,
20,41,21,43,22,45,23,47,24,49,25,51,26,53,27,55,28,57,0,59,29,61,30,63,
0,65,0,67,0,69,31,71,32,73,0,75,33,1,0,29,2,0,76,76,108,108,2,0,73,73,105,
105,2,0,75,75,107,107,2,0,69,69,101,101,2,0,66,66,98,98,2,0,84,84,116,116,
2,0,87,87,119,119,2,0,78,78,110,110,2,0,88,88,120,120,2,0,83,83,115,115,
2,0,82,82,114,114,2,0,71,71,103,103,2,0,80,80,112,112,2,0,67,67,99,99,2,
0,79,79,111,111,2,0,65,65,97,97,2,0,68,68,100,100,2,0,72,72,104,104,2,0,
89,89,121,121,2,0,85,85,117,117,2,0,70,70,102,102,2,0,43,43,45,45,2,0,34,
34,92,92,2,0,39,39,92,92,4,0,35,36,64,90,95,95,97,123,7,0,35,36,45,45,47,
58,64,90,95,95,97,123,125,125,3,0,9,10,13,13,32,32,1,0,48,57,8,0,9,10,13,
13,32,34,39,41,44,44,60,62,91,91,93,93,353,0,1,1,0,0,0,0,3,1,0,0,0,0,5,
1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,
0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,
0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,
0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,
0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,59,1,0,0,0,0,61,1,0,0,0,
0,69,1,0,0,0,0,71,1,0,0,0,0,75,1,0,0,0,1,77,1,0,0,0,3,79,1,0,0,0,5,81,1,
0,0,0,7,83,1,0,0,0,9,85,1,0,0,0,11,90,1,0,0,0,13,92,1,0,0,0,15,95,1,0,0,
0,17,98,1,0,0,0,19,100,1,0,0,0,21,103,1,0,0,0,23,105,1,0,0,0,25,108,1,0,
0,0,27,113,1,0,0,0,29,119,1,0,0,0,31,127,1,0,0,0,33,135,1,0,0,0,35,142,
1,0,0,0,37,152,1,0,0,0,39,155,1,0,0,0,41,159,1,0,0,0,43,163,1,0,0,0,45,
166,1,0,0,0,47,175,1,0,0,0,49,179,1,0,0,0,51,186,1,0,0,0,53,193,1,0,0,0,
55,209,1,0,0,0,57,211,1,0,0,0,59,261,1,0,0,0,61,283,1,0,0,0,63,285,1,0,
0,0,65,292,1,0,0,0,67,295,1,0,0,0,69,299,1,0,0,0,71,316,1,0,0,0,73,322,
1,0,0,0,75,325,1,0,0,0,77,78,5,40,0,0,78,2,1,0,0,0,79,80,5,41,0,0,80,4,
1,0,0,0,81,82,5,91,0,0,82,6,1,0,0,0,83,84,5,93,0,0,84,8,1,0,0,0,85,86,5,
44,0,0,86,10,1,0,0,0,87,91,5,61,0,0,88,89,5,61,0,0,89,91,5,61,0,0,90,87,
1,0,0,0,90,88,1,0,0,0,91,12,1,0,0,0,92,93,5,33,0,0,93,94,5,61,0,0,94,14,
1,0,0,0,95,96,5,60,0,0,96,97,5,62,0,0,97,16,1,0,0,0,98,99,5,60,0,0,99,18,
1,0,0,0,100,101,5,60,0,0,101,102,5,61,0,0,102,20,1,0,0,0,103,104,5,62,0,
0,104,22,1,0,0,0,105,106,5,62,0,0,106,107,5,61,0,0,107,24,1,0,0,0,108,109,
7,0,0,0,109,110,7,1,0,0,110,111,7,2,0,0,111,112,7,3,0,0,112,26,1,0,0,0,
113,114,7,1,0,0,114,115,7,0,0,0,115,116,7,1,0,0,116,117,7,2,0,0,117,118,
7,3,0,0,118,28,1,0,0,0,119,120,7,4,0,0,120,121,7,3,0,0,121,122,7,5,0,0,
122,123,7,6,0,0,123,124,7,3,0,0,124,125,7,3,0,0,125,126,7,7,0,0,126,30,
1,0,0,0,127,128,7,3,0,0,128,129,7,8,0,0,129,130,7,1,0,0,130,131,7,9,0,0,
131,133,7,5,0,0,132,134,7,9,0,0,133,132,1,0,0,0,133,134,1,0,0,0,134,32,
1,0,0,0,135,136,7,10,0,0,136,137,7,3,0,0,137,138,7,11,0,0,138,139,7,3,0,
0,139,140,7,8,0,0,140,141,7,12,0,0,141,34,1,0,0,0,142,143,7,13,0,0,143,
144,7,14,0,0,144,145,7,7,0,0,145,146,7,5,0,0,146,147,7,15,0,0,147,148,7,
1,0,0,148,150,7,7,0,0,149,151,7,9,0,0,150,149,1,0,0,0,150,151,1,0,0,0,151,
36,1,0,0,0,152,153,7,1,0,0,153,154,7,7,0,0,154,38,1,0,0,0,155,156,7,7,0,
0,156,157,7,14,0,0,157,158,7,5,0,0,158,40,1,0,0,0,159,160,7,15,0,0,160,
161,7,7,0,0,161,162,7,16,0,0,162,42,1,0,0,0,163,164,7,14,0,0,164,165,7,
10,0,0,165,44,1,0,0,0,166,167,7,17,0,0,167,168,7,15,0,0,168,169,7,9,0,0,
169,170,7,5,0,0,170,171,7,14,0,0,171,172,7,2,0,0,172,173,7,3,0,0,173,174,
7,7,0,0,174,46,1,0,0,0,175,176,7,17,0,0,176,177,7,15,0,0,177,178,7,9,0,
0,178,48,1,0,0,0,179,180,7,17,0,0,180,181,7,15,0,0,181,182,7,9,0,0,182,
183,7,15,0,0,183,184,7,7,0,0,184,185,7,18,0,0,185,50,1,0,0,0,186,187,7,
17,0,0,187,188,7,15,0,0,188,189,7,9,0,0,189,190,7,15,0,0,190,191,7,0,0,
0,191,192,7,0,0,0,192,52,1,0,0,0,193,194,7,9,0,0,194,195,7,3,0,0,195,196,
7,15,0,0,196,197,7,10,0,0,197,198,7,13,0,0,198,199,7,17,0,0,199,54,1,0,
0,0,200,201,7,5,0,0,201,202,7,10,0,0,202,203,7,19,0,0,203,210,7,3,0,0,204,
205,7,20,0,0,205,206,7,15,0,0,206,207,7,0,0,0,207,208,7,9,0,0,208,210,7,
3,0,0,209,200,1,0,0,0,209,204,1,0,0,0,210,56,1,0,0,0,211,212,7,21,0,0,212,
58,1,0,0,0,213,215,3,57,28,0,214,213,1,0,0,0,214,215,1,0,0,0,215,217,1,
0,0,0,216,218,3,73,36,0,217,216,1,0,0,0,218,219,1,0,0,0,219,217,1,0,0,0,
219,220,1,0,0,0,220,228,1,0,0,0,221,225,5,46,0,0,222,224,3,73,36,0,223,
222,1,0,0,0,224,227,1,0,0,0,225,223,1,0,0,0,225,226,1,0,0,0,226,229,1,0,
0,0,227,225,1,0,0,0,228,221,1,0,0,0,228,229,1,0,0,0,229,239,1,0,0,0,230,
232,7,3,0,0,231,233,3,57,28,0,232,231,1,0,0,0,232,233,1,0,0,0,233,235,1,
0,0,0,234,236,3,73,36,0,235,234,1,0,0,0,236,237,1,0,0,0,237,235,1,0,0,0,
237,238,1,0,0,0,238,240,1,0,0,0,239,230,1,0,0,0,239,240,1,0,0,0,240,262,
1,0,0,0,241,243,3,57,28,0,242,241,1,0,0,0,242,243,1,0,0,0,243,244,1,0,0,
0,244,246,5,46,0,0,245,247,3,73,36,0,246,245,1,0,0,0,247,248,1,0,0,0,248,
246,1,0,0,0,248,249,1,0,0,0,249,259,1,0,0,0,250,252,7,3,0,0,251,253,3,57,
28,0,252,251,1,0,0,0,252,253,1,0,0,0,253,255,1,0,0,0,254,256,3,73,36,0,
255,254,1,0,0,0,256,257,1,0,0,0,257,255,1,0,0,0,257,258,1,0,0,0,258,260,
1,0,0,0,259,250,1,0,0,0,259,260,1,0,0,0,260,262,1,0,0,0,261,214,1,0,0,0,
261,242,1,0,0,0,262,60,1,0,0,0,263,269,5,34,0,0,264,268,8,22,0,0,265,266,
5,92,0,0,266,268,9,0,0,0,267,264,1,0,0,0,267,265,1,0,0,0,268,271,1,0,0,
0,269,267,1,0,0,0,269,270,1,0,0,0,270,272,1,0,0,0,271,269,1,0,0,0,272,284,
5,34,0,0,273,279,5,39,0,0,274,278,8,23,0,0,275,276,5,92,0,0,276,278,9,0,
0,0,277,274,1,0,0,0,277,275,1,0,0,0,278,281,1,0,0,0,279,277,1,0,0,0,279,
280,1,0,0,0,280,282,1,0,0,0,281,279,1,0,0,0,282,284,5,39,0,0,283,263,1,
0,0,0,283,273,1,0,0,0,284,62,1,0,0,0,285,289,7,24,0,0,286,288,7,25,0,0,
287,286,1,0,0,0,288,291,1,0,0,0,289,287,1,0,0,0,289,290,1,0,0,0,290,64,
1,0,0,0,291,289,1,0,0,0,292,293,5,91,0,0,293,294,5,93,0,0,294,66,1,0,0,
0,295,296,5,91,0,0,296,297,5,42,0,0,297,298,5,93,0,0,298,68,1,0,0,0,299,
312,3,63,31,0,300,301,5,46,0,0,301,311,3,63,31,0,302,311,3,65,32,0,303,
311,3,67,33,0,304,306,5,46,0,0,305,307,3,73,36,0,306,305,1,0,0,0,307,308,
1,0,0,0,308,306,1,0,0,0,308,309,1,0,0,0,309,311,1,0,0,0,310,300,1,0,0,0,
310,302,1,0,0,0,310,303,1,0,0,0,310,304,1,0,0,0,311,314,1,0,0,0,312,310,
1,0,0,0,312,313,1,0,0,0,313,70,1,0,0,0,314,312,1,0,0,0,315,317,7,26,0,0,
316,315,1,0,0,0,317,318,1,0,0,0,318,316,1,0,0,0,318,319,1,0,0,0,319,320,
1,0,0,0,320,321,6,35,0,0,321,72,1,0,0,0,322,323,7,27,0,0,323,74,1,0,0,0,
324,326,8,28,0,0,325,324,1,0,0,0,326,327,1,0,0,0,327,325,1,0,0,0,327,328,
1,0,0,0,328,76,1,0,0,0,29,0,90,133,150,209,214,219,225,228,232,237,239,
242,248,252,257,259,261,267,269,277,279,283,289,308,310,312,318,327,1,6,
0,0];
31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,1,0,1,0,1,1,1,
1,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,3,5,89,8,5,1,6,1,6,1,6,1,7,1,7,1,
7,1,8,1,8,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,
1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,
15,1,15,1,15,1,15,1,15,1,15,3,15,132,8,15,1,16,1,16,1,16,1,16,1,16,1,16,
1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,3,17,149,8,17,1,18,1,18,1,
18,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,22,1,22,1,22,
1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,24,1,24,1,24,1,24,1,
24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,1,26,1,26,
1,26,1,26,1,26,1,26,3,26,201,8,26,1,27,1,27,1,28,3,28,206,8,28,1,28,4,28,
209,8,28,11,28,12,28,210,1,28,1,28,5,28,215,8,28,10,28,12,28,218,9,28,3,
28,220,8,28,1,28,1,28,3,28,224,8,28,1,28,4,28,227,8,28,11,28,12,28,228,
3,28,231,8,28,1,28,3,28,234,8,28,1,28,1,28,4,28,238,8,28,11,28,12,28,239,
1,28,1,28,3,28,244,8,28,1,28,4,28,247,8,28,11,28,12,28,248,3,28,251,8,28,
3,28,253,8,28,1,29,1,29,1,29,1,29,5,29,259,8,29,10,29,12,29,262,9,29,1,
29,1,29,1,29,1,29,1,29,5,29,269,8,29,10,29,12,29,272,9,29,1,29,3,29,275,
8,29,1,30,1,30,5,30,279,8,30,10,30,12,30,282,9,30,1,31,1,31,1,31,1,32,1,
32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,33,1,33,4,33,298,8,33,11,33,12,
33,299,5,33,302,8,33,10,33,12,33,305,9,33,1,34,4,34,308,8,34,11,34,12,34,
309,1,34,1,34,1,35,1,35,1,36,4,36,317,8,36,11,36,12,36,318,0,0,37,1,1,3,
2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,
16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,51,26,53,27,55,
0,57,28,59,29,61,0,63,0,65,0,67,30,69,31,71,0,73,32,1,0,29,2,0,76,76,108,
108,2,0,73,73,105,105,2,0,75,75,107,107,2,0,69,69,101,101,2,0,66,66,98,
98,2,0,84,84,116,116,2,0,87,87,119,119,2,0,78,78,110,110,2,0,88,88,120,
120,2,0,83,83,115,115,2,0,82,82,114,114,2,0,71,71,103,103,2,0,80,80,112,
112,2,0,67,67,99,99,2,0,79,79,111,111,2,0,65,65,97,97,2,0,68,68,100,100,
2,0,72,72,104,104,2,0,89,89,121,121,2,0,85,85,117,117,2,0,70,70,102,102,
2,0,43,43,45,45,2,0,34,34,92,92,2,0,39,39,92,92,4,0,35,36,64,90,95,95,97,
123,7,0,35,36,45,45,47,58,64,90,95,95,97,123,125,125,3,0,9,10,13,13,32,
32,1,0,48,57,8,0,9,10,13,13,32,34,39,41,44,44,60,62,91,91,93,93,344,0,1,
1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,
13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,
0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,
35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,
0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,57,1,0,0,0,0,
59,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,73,1,0,0,0,1,75,1,0,0,0,3,77,1,0,
0,0,5,79,1,0,0,0,7,81,1,0,0,0,9,83,1,0,0,0,11,88,1,0,0,0,13,90,1,0,0,0,
15,93,1,0,0,0,17,96,1,0,0,0,19,98,1,0,0,0,21,101,1,0,0,0,23,103,1,0,0,0,
25,106,1,0,0,0,27,111,1,0,0,0,29,117,1,0,0,0,31,125,1,0,0,0,33,133,1,0,
0,0,35,140,1,0,0,0,37,150,1,0,0,0,39,153,1,0,0,0,41,157,1,0,0,0,43,161,
1,0,0,0,45,164,1,0,0,0,47,173,1,0,0,0,49,177,1,0,0,0,51,184,1,0,0,0,53,
200,1,0,0,0,55,202,1,0,0,0,57,252,1,0,0,0,59,274,1,0,0,0,61,276,1,0,0,0,
63,283,1,0,0,0,65,286,1,0,0,0,67,290,1,0,0,0,69,307,1,0,0,0,71,313,1,0,
0,0,73,316,1,0,0,0,75,76,5,40,0,0,76,2,1,0,0,0,77,78,5,41,0,0,78,4,1,0,
0,0,79,80,5,91,0,0,80,6,1,0,0,0,81,82,5,93,0,0,82,8,1,0,0,0,83,84,5,44,
0,0,84,10,1,0,0,0,85,89,5,61,0,0,86,87,5,61,0,0,87,89,5,61,0,0,88,85,1,
0,0,0,88,86,1,0,0,0,89,12,1,0,0,0,90,91,5,33,0,0,91,92,5,61,0,0,92,14,1,
0,0,0,93,94,5,60,0,0,94,95,5,62,0,0,95,16,1,0,0,0,96,97,5,60,0,0,97,18,
1,0,0,0,98,99,5,60,0,0,99,100,5,61,0,0,100,20,1,0,0,0,101,102,5,62,0,0,
102,22,1,0,0,0,103,104,5,62,0,0,104,105,5,61,0,0,105,24,1,0,0,0,106,107,
7,0,0,0,107,108,7,1,0,0,108,109,7,2,0,0,109,110,7,3,0,0,110,26,1,0,0,0,
111,112,7,1,0,0,112,113,7,0,0,0,113,114,7,1,0,0,114,115,7,2,0,0,115,116,
7,3,0,0,116,28,1,0,0,0,117,118,7,4,0,0,118,119,7,3,0,0,119,120,7,5,0,0,
120,121,7,6,0,0,121,122,7,3,0,0,122,123,7,3,0,0,123,124,7,7,0,0,124,30,
1,0,0,0,125,126,7,3,0,0,126,127,7,8,0,0,127,128,7,1,0,0,128,129,7,9,0,0,
129,131,7,5,0,0,130,132,7,9,0,0,131,130,1,0,0,0,131,132,1,0,0,0,132,32,
1,0,0,0,133,134,7,10,0,0,134,135,7,3,0,0,135,136,7,11,0,0,136,137,7,3,0,
0,137,138,7,8,0,0,138,139,7,12,0,0,139,34,1,0,0,0,140,141,7,13,0,0,141,
142,7,14,0,0,142,143,7,7,0,0,143,144,7,5,0,0,144,145,7,15,0,0,145,146,7,
1,0,0,146,148,7,7,0,0,147,149,7,9,0,0,148,147,1,0,0,0,148,149,1,0,0,0,149,
36,1,0,0,0,150,151,7,1,0,0,151,152,7,7,0,0,152,38,1,0,0,0,153,154,7,7,0,
0,154,155,7,14,0,0,155,156,7,5,0,0,156,40,1,0,0,0,157,158,7,15,0,0,158,
159,7,7,0,0,159,160,7,16,0,0,160,42,1,0,0,0,161,162,7,14,0,0,162,163,7,
10,0,0,163,44,1,0,0,0,164,165,7,17,0,0,165,166,7,15,0,0,166,167,7,9,0,0,
167,168,7,5,0,0,168,169,7,14,0,0,169,170,7,2,0,0,170,171,7,3,0,0,171,172,
7,7,0,0,172,46,1,0,0,0,173,174,7,17,0,0,174,175,7,15,0,0,175,176,7,9,0,
0,176,48,1,0,0,0,177,178,7,17,0,0,178,179,7,15,0,0,179,180,7,9,0,0,180,
181,7,15,0,0,181,182,7,7,0,0,182,183,7,18,0,0,183,50,1,0,0,0,184,185,7,
17,0,0,185,186,7,15,0,0,186,187,7,9,0,0,187,188,7,15,0,0,188,189,7,0,0,
0,189,190,7,0,0,0,190,52,1,0,0,0,191,192,7,5,0,0,192,193,7,10,0,0,193,194,
7,19,0,0,194,201,7,3,0,0,195,196,7,20,0,0,196,197,7,15,0,0,197,198,7,0,
0,0,198,199,7,9,0,0,199,201,7,3,0,0,200,191,1,0,0,0,200,195,1,0,0,0,201,
54,1,0,0,0,202,203,7,21,0,0,203,56,1,0,0,0,204,206,3,55,27,0,205,204,1,
0,0,0,205,206,1,0,0,0,206,208,1,0,0,0,207,209,3,71,35,0,208,207,1,0,0,0,
209,210,1,0,0,0,210,208,1,0,0,0,210,211,1,0,0,0,211,219,1,0,0,0,212,216,
5,46,0,0,213,215,3,71,35,0,214,213,1,0,0,0,215,218,1,0,0,0,216,214,1,0,
0,0,216,217,1,0,0,0,217,220,1,0,0,0,218,216,1,0,0,0,219,212,1,0,0,0,219,
220,1,0,0,0,220,230,1,0,0,0,221,223,7,3,0,0,222,224,3,55,27,0,223,222,1,
0,0,0,223,224,1,0,0,0,224,226,1,0,0,0,225,227,3,71,35,0,226,225,1,0,0,0,
227,228,1,0,0,0,228,226,1,0,0,0,228,229,1,0,0,0,229,231,1,0,0,0,230,221,
1,0,0,0,230,231,1,0,0,0,231,253,1,0,0,0,232,234,3,55,27,0,233,232,1,0,0,
0,233,234,1,0,0,0,234,235,1,0,0,0,235,237,5,46,0,0,236,238,3,71,35,0,237,
236,1,0,0,0,238,239,1,0,0,0,239,237,1,0,0,0,239,240,1,0,0,0,240,250,1,0,
0,0,241,243,7,3,0,0,242,244,3,55,27,0,243,242,1,0,0,0,243,244,1,0,0,0,244,
246,1,0,0,0,245,247,3,71,35,0,246,245,1,0,0,0,247,248,1,0,0,0,248,246,1,
0,0,0,248,249,1,0,0,0,249,251,1,0,0,0,250,241,1,0,0,0,250,251,1,0,0,0,251,
253,1,0,0,0,252,205,1,0,0,0,252,233,1,0,0,0,253,58,1,0,0,0,254,260,5,34,
0,0,255,259,8,22,0,0,256,257,5,92,0,0,257,259,9,0,0,0,258,255,1,0,0,0,258,
256,1,0,0,0,259,262,1,0,0,0,260,258,1,0,0,0,260,261,1,0,0,0,261,263,1,0,
0,0,262,260,1,0,0,0,263,275,5,34,0,0,264,270,5,39,0,0,265,269,8,23,0,0,
266,267,5,92,0,0,267,269,9,0,0,0,268,265,1,0,0,0,268,266,1,0,0,0,269,272,
1,0,0,0,270,268,1,0,0,0,270,271,1,0,0,0,271,273,1,0,0,0,272,270,1,0,0,0,
273,275,5,39,0,0,274,254,1,0,0,0,274,264,1,0,0,0,275,60,1,0,0,0,276,280,
7,24,0,0,277,279,7,25,0,0,278,277,1,0,0,0,279,282,1,0,0,0,280,278,1,0,0,
0,280,281,1,0,0,0,281,62,1,0,0,0,282,280,1,0,0,0,283,284,5,91,0,0,284,285,
5,93,0,0,285,64,1,0,0,0,286,287,5,91,0,0,287,288,5,42,0,0,288,289,5,93,
0,0,289,66,1,0,0,0,290,303,3,61,30,0,291,292,5,46,0,0,292,302,3,61,30,0,
293,302,3,63,31,0,294,302,3,65,32,0,295,297,5,46,0,0,296,298,3,71,35,0,
297,296,1,0,0,0,298,299,1,0,0,0,299,297,1,0,0,0,299,300,1,0,0,0,300,302,
1,0,0,0,301,291,1,0,0,0,301,293,1,0,0,0,301,294,1,0,0,0,301,295,1,0,0,0,
302,305,1,0,0,0,303,301,1,0,0,0,303,304,1,0,0,0,304,68,1,0,0,0,305,303,
1,0,0,0,306,308,7,26,0,0,307,306,1,0,0,0,308,309,1,0,0,0,309,307,1,0,0,
0,309,310,1,0,0,0,310,311,1,0,0,0,311,312,6,34,0,0,312,70,1,0,0,0,313,314,
7,27,0,0,314,72,1,0,0,0,315,317,8,28,0,0,316,315,1,0,0,0,317,318,1,0,0,
0,318,316,1,0,0,0,318,319,1,0,0,0,319,74,1,0,0,0,29,0,88,131,148,200,205,
210,216,219,223,228,230,233,239,243,248,250,252,258,260,268,270,274,280,
299,301,303,309,318,1,6,0,0];
private static __ATN: ATN;
public static get _ATN(): ATN {

View File

@@ -1,25 +1,25 @@
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
// Generated from FilterQuery.g4 by ANTLR 4.13.1
import {ParseTreeListener} from "antlr4";
import { QueryContext } from "./FilterQueryParser.js";
import { ExpressionContext } from "./FilterQueryParser.js";
import { OrExpressionContext } from "./FilterQueryParser.js";
import { AndExpressionContext } from "./FilterQueryParser.js";
import { UnaryExpressionContext } from "./FilterQueryParser.js";
import { PrimaryContext } from "./FilterQueryParser.js";
import { ComparisonContext } from "./FilterQueryParser.js";
import { InClauseContext } from "./FilterQueryParser.js";
import { NotInClauseContext } from "./FilterQueryParser.js";
import { ValueListContext } from "./FilterQueryParser.js";
import { FullTextContext } from "./FilterQueryParser.js";
import { FunctionCallContext } from "./FilterQueryParser.js";
import { FunctionParamListContext } from "./FilterQueryParser.js";
import { FunctionParamContext } from "./FilterQueryParser.js";
import { ArrayContext } from "./FilterQueryParser.js";
import { ValueContext } from "./FilterQueryParser.js";
import { KeyContext } from "./FilterQueryParser.js";
import { QueryContext } from "./FilterQueryParser";
import { ExpressionContext } from "./FilterQueryParser";
import { OrExpressionContext } from "./FilterQueryParser";
import { AndExpressionContext } from "./FilterQueryParser";
import { UnaryExpressionContext } from "./FilterQueryParser";
import { PrimaryContext } from "./FilterQueryParser";
import { ComparisonContext } from "./FilterQueryParser";
import { InClauseContext } from "./FilterQueryParser";
import { NotInClauseContext } from "./FilterQueryParser";
import { ValueListContext } from "./FilterQueryParser";
import { FullTextContext } from "./FilterQueryParser";
import { FunctionCallContext } from "./FilterQueryParser";
import { FunctionParamListContext } from "./FilterQueryParser";
import { FunctionParamContext } from "./FilterQueryParser";
import { ArrayContext } from "./FilterQueryParser";
import { ValueContext } from "./FilterQueryParser";
import { KeyContext } from "./FilterQueryParser";
/**

View File

@@ -1,4 +1,4 @@
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
// Generated from FilterQuery.g4 by ANTLR 4.13.1
// noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols
import {
@@ -45,14 +45,13 @@ export default class FilterQueryParser extends Parser {
public static readonly HAS = 24;
public static readonly HASANY = 25;
public static readonly HASALL = 26;
public static readonly SEARCH = 27;
public static readonly BOOL = 28;
public static readonly NUMBER = 29;
public static readonly QUOTED_TEXT = 30;
public static readonly KEY = 31;
public static readonly WS = 32;
public static readonly FREETEXT = 33;
public static override readonly EOF = Token.EOF;
public static readonly BOOL = 27;
public static readonly NUMBER = 28;
public static readonly QUOTED_TEXT = 29;
public static readonly KEY = 30;
public static readonly WS = 31;
public static readonly FREETEXT = 32;
public static readonly EOF = Token.EOF;
public static readonly RULE_query = 0;
public static readonly RULE_expression = 1;
public static readonly RULE_orExpression = 2;
@@ -91,9 +90,8 @@ export default class FilterQueryParser extends Parser {
"AND", "OR",
"HASTOKEN",
"HAS", "HASANY",
"HASALL", "SEARCH",
"BOOL", "NUMBER",
"QUOTED_TEXT",
"HASALL", "BOOL",
"NUMBER", "QUOTED_TEXT",
"KEY", "WS",
"FREETEXT" ];
// tslint:disable:no-trailing-whitespace
@@ -224,7 +222,7 @@ export default class FilterQueryParser extends Parser {
this.state = 53;
this._errHandler.sync(this);
_la = this._input.LA(1);
while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 4289724418) !== 0) || _la===33) {
while (((((_la - 1)) & ~0x1F) === 0 && ((1 << (_la - 1)) & 3218604033) !== 0)) {
{
this.state = 51;
this._errHandler.sync(this);
@@ -247,8 +245,7 @@ export default class FilterQueryParser extends Parser {
case 28:
case 29:
case 30:
case 31:
case 33:
case 32:
{
this.state = 50;
this.unaryExpression();
@@ -814,7 +811,7 @@ export default class FilterQueryParser extends Parser {
{
this.state = 190;
_la = this._input.LA(1);
if(!(_la===30 || _la===33)) {
if(!(_la===29 || _la===32)) {
this._errHandler.recoverInline(this);
}
else {
@@ -847,7 +844,7 @@ export default class FilterQueryParser extends Parser {
{
this.state = 192;
_la = this._input.LA(1);
if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 260046848) !== 0))) {
if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 125829120) !== 0))) {
this._errHandler.recoverInline(this);
}
else {
@@ -1002,7 +999,7 @@ export default class FilterQueryParser extends Parser {
{
this.state = 214;
_la = this._input.LA(1);
if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 4026531840) !== 0))) {
if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 2013265920) !== 0))) {
this._errHandler.recoverInline(this);
}
else {
@@ -1051,7 +1048,7 @@ export default class FilterQueryParser extends Parser {
return localctx;
}
public static readonly _serializedATN: number[] = [4,1,33,219,2,0,7,0,2,
public static readonly _serializedATN: number[] = [4,1,32,219,2,0,7,0,2,
1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,
10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,1,0,
1,0,1,0,1,1,1,1,1,2,1,2,1,2,5,2,43,8,2,10,2,12,2,46,9,2,1,3,1,3,1,3,1,3,
@@ -1066,7 +1063,7 @@ export default class FilterQueryParser extends Parser {
10,9,12,9,189,9,9,1,10,1,10,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,5,12,
201,8,12,10,12,12,12,204,9,12,1,13,1,13,1,13,3,13,209,8,13,1,14,1,14,1,
14,1,14,1,15,1,15,1,16,1,16,1,16,0,0,17,0,2,4,6,8,10,12,14,16,18,20,22,
24,26,28,30,32,0,5,1,0,7,8,1,0,13,14,2,0,30,30,33,33,1,0,23,27,1,0,28,31,
24,26,28,30,32,0,5,1,0,7,8,1,0,13,14,2,0,29,29,32,32,1,0,23,26,1,0,27,30,
235,0,34,1,0,0,0,2,37,1,0,0,0,4,39,1,0,0,0,6,47,1,0,0,0,8,57,1,0,0,0,10,
70,1,0,0,0,12,149,1,0,0,0,14,163,1,0,0,0,16,180,1,0,0,0,18,182,1,0,0,0,
20,190,1,0,0,0,22,192,1,0,0,0,24,197,1,0,0,0,26,208,1,0,0,0,28,210,1,0,
@@ -1118,7 +1115,7 @@ export default class FilterQueryParser extends Parser {
0,0,205,209,3,32,16,0,206,209,3,30,15,0,207,209,3,28,14,0,208,205,1,0,0,
0,208,206,1,0,0,0,208,207,1,0,0,0,209,27,1,0,0,0,210,211,5,3,0,0,211,212,
3,18,9,0,212,213,5,4,0,0,213,29,1,0,0,0,214,215,7,4,0,0,215,31,1,0,0,0,
216,217,5,31,0,0,217,33,1,0,0,0,11,44,51,53,57,70,149,163,180,187,202,208];
216,217,5,30,0,0,217,33,1,0,0,0,11,44,51,53,57,70,149,163,180,187,202,208];
private static __ATN: ATN;
public static get _ATN(): ATN {
@@ -1665,9 +1662,6 @@ export class FunctionCallContext extends ParserRuleContext {
public HASALL(): TerminalNode {
return this.getToken(FilterQueryParser.HASALL, 0);
}
public SEARCH(): TerminalNode {
return this.getToken(FilterQueryParser.SEARCH, 0);
}
public get ruleIndex(): number {
return FilterQueryParser.RULE_functionCall;
}

View File

@@ -1,25 +1,25 @@
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
// Generated from FilterQuery.g4 by ANTLR 4.13.1
import {ParseTreeVisitor} from 'antlr4';
import { QueryContext } from "./FilterQueryParser.js";
import { ExpressionContext } from "./FilterQueryParser.js";
import { OrExpressionContext } from "./FilterQueryParser.js";
import { AndExpressionContext } from "./FilterQueryParser.js";
import { UnaryExpressionContext } from "./FilterQueryParser.js";
import { PrimaryContext } from "./FilterQueryParser.js";
import { ComparisonContext } from "./FilterQueryParser.js";
import { InClauseContext } from "./FilterQueryParser.js";
import { NotInClauseContext } from "./FilterQueryParser.js";
import { ValueListContext } from "./FilterQueryParser.js";
import { FullTextContext } from "./FilterQueryParser.js";
import { FunctionCallContext } from "./FilterQueryParser.js";
import { FunctionParamListContext } from "./FilterQueryParser.js";
import { FunctionParamContext } from "./FilterQueryParser.js";
import { ArrayContext } from "./FilterQueryParser.js";
import { ValueContext } from "./FilterQueryParser.js";
import { KeyContext } from "./FilterQueryParser.js";
import { QueryContext } from "./FilterQueryParser";
import { ExpressionContext } from "./FilterQueryParser";
import { OrExpressionContext } from "./FilterQueryParser";
import { AndExpressionContext } from "./FilterQueryParser";
import { UnaryExpressionContext } from "./FilterQueryParser";
import { PrimaryContext } from "./FilterQueryParser";
import { ComparisonContext } from "./FilterQueryParser";
import { InClauseContext } from "./FilterQueryParser";
import { NotInClauseContext } from "./FilterQueryParser";
import { ValueListContext } from "./FilterQueryParser";
import { FullTextContext } from "./FilterQueryParser";
import { FunctionCallContext } from "./FilterQueryParser";
import { FunctionParamListContext } from "./FilterQueryParser";
import { FunctionParamContext } from "./FilterQueryParser";
import { ArrayContext } from "./FilterQueryParser";
import { ValueContext } from "./FilterQueryParser";
import { KeyContext } from "./FilterQueryParser";
/**

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