Compare commits

...

143 Commits

Author SHA1 Message Date
amlannandy
c92161b32d chore: initial playwright setup 2025-10-11 17:14:13 +07:00
amlannandy
e744128401 chore: fix playwright base setup 2025-10-09 16:27:52 +07:00
amlannandy
9ff4a01633 chore: push branch 2025-10-05 22:25:24 +07:00
Amlan Kumar Nandy
c770a1a4e1 chore: remove routing policies feature flag (#9249) 2025-10-04 21:39:18 +05:30
Nageshbansal
101b3668b5 chore(statsreporter): fix azure IMDS endpoint in statsreporter (#9243) 2025-10-03 17:00:06 +00:00
Srikanth Chekuri
1b1aa4915b chore: link to docs, QB flavour for expr and update options (#9246) 2025-10-03 21:50:56 +05:30
aniketio-ctrl
f9a70a3a69 chore: notification routing | added notificaiton routing via expression based routes (#9195)
* chore: added custom distpatcher

* feat(notification-grouping): added notification grouping

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): linting and test cases

* feat(notification-grouping): linting and test cases

* feat(notification-grouping): linting and test cases

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): debug log lines

* feat(notification-grouping): debug log lines

* feat(notification-grouping): debug log lines

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): addded integration test dependency

* feat(notification-grouping): added structure changes

* feat(notification-grouping): added structure changes

* feat(notification-routing): added notification routing

* chore(notification-grouping): added notificaiton grouping

* Update pkg/alertmanager/nfmanager/rulebasednotification/provider.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-grouping): added renotification interval

* fix(notification-grouping): added fix for renotification

* chore(notificaiton-grouping): added no data renotify

* chore(notificaiton-grouping): added no data renotify

* chore(notificaiton-grouping): added no data renotify

* chore(notification-grouping): added no data renotify interval

* chore(notification-grouping): removed errors package from dispatcher

* chore(notification-grouping): removed errors package from dispatcher

* chore(notification-grouping): removed unwanted tests

* chore(notification-grouping): removed unwanted pkg name

* chore(notification-grouping): added delete notification setting

* chore(notification-grouping): added delete notification setting

* Update pkg/alertmanager/nfmanager/nfmanagertest/provider.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-grouping): removed nfmanager config| notification settings in postable rule

* chore(notification-grouping): removed nfmanager config| notification settings in postable rule

* chore(notification-grouping): added test for dispatcher

* chore(notification-grouping): added test for dispatcher

* chore(notification-grouping): go linting errors

* chore(notification-grouping): added test cases for aggGroupPerRoute

* chore(notification-grouping): added test cases for aggGroupPerRoute

* chore(notification-grouping): corrected get notification config logic

* Update pkg/alertmanager/nfmanager/rulebasednotification/provider_test.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-routing): added notification routing policies

* feat(notification-routing): added test cases for dispatcher

* chore(notification-routing): added notification routing policies

* chore(notification-routing): added notification routing policies

* Apply suggestions from code review

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-routing): added notification routing policies

* chore(notification-routing): added notification routing policies

* Update pkg/alertmanager/alertmanagerserver/distpatcher_test.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore(notification-routing): sorted imports

* chore(notification-routing): minor edit |pr resolve comments

* chore(notification-grouping): corrected dispatcher test cases

* chore(notification-routing): added notification routing policies

* chore(notification-routing): corrected race condition in test

* chore: resolved pr comments

* chore: passing threshold value to tempalte

* chore: completed delete rule functionality

* chore: added grouping disabled functionality

* chore: added grouping disabled functionality

* chore(notification-routing): resolved pr comments

* chore(notification-routing): resolved pr comments

* chore(notification-routing): resolved pr comments

* chore(notification-routing): sorted imports

* chore(notification-routing): fix linting errors

* chore(notification-routing): removed enabled flags

* fix: test rule multiple threhsold (#9224)

* chore: corrected linting errors

* chore: corrected linting errors

* chore: corrected linting errors

* chore: corrected linting errors

* chore: corrected migration errors

* chore: corrected migration errors

* chore: corrected migration errors

* chore: corrected migration errors

* Update pkg/sqlmigration/049_add_route_policy.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* chore: added org_is as foreign key

* chore: resolved pr comments

* chore: removed route store unused

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-10-03 19:47:15 +05:30
Nityananda Gohain
d3be2632b6 fix: exists/not exists for resource attributes (#9129)
* fix: exists/not exists for resource attributes

* fix: update tests

* fix: remove unwanted changes

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-10-03 05:42:42 +00:00
Srikanth Chekuri
78e4f4f386 fix: handle resource/attribute context collision with expression using paranthesis (#9240) 2025-10-03 10:16:06 +05:30
Amlan Kumar Nandy
cbb24d9a34 chore: edit alerts api integration (#9210) 2025-09-30 17:37:47 +00:00
Nageshbansal
9ffe0d8143 chore(statsreporter): add nomad, vultr, aca platform detection (#9220) 2025-09-30 16:31:43 +05:30
Amlan Kumar Nandy
1a1ef5aff8 chore: create alerts ux improvements and api integration (#9165) 2025-09-29 17:03:29 +00:00
Abhi kumar
8b21ba5db9 ISSUE:2806 - View traces/logs functionality across the product with new QB (#9207)
* fix: issue-2806 view traces/logs functionality across the product with new qb

* test: added test for getfilter

* test: updated tests
2025-09-29 19:12:50 +05:30
Vikrant Gupta
1b818dd05d feat(authz): build role module (#9136)
* feat(authz): build role module

* feat(authz): build role module

* feat(authz): refactor the role module to move transactions out

* feat(authz): add handler implementation except patch objects

* feat(authz): added the missing handler

* feat(authz): added changes for selectors

* feat(authz): added changes for selectors

* feat(authz): added changes for selectors

* feat(authz): make the role create handler just to create metadata

* feat(authz): address review comments

* feat(authz): address review comments

* feat(authz): address review comments

* feat(authz): address review comments
2025-09-29 17:45:52 +05:30
Aditya Singh
3c3641493e fix: fix page offset in exceptions tab (#9184) 2025-09-28 12:45:56 +00:00
Amlan Kumar Nandy
411414fa45 chore: add routing polices page (#9198) 2025-09-27 21:02:14 +05:30
aniketio-ctrl
735b90722d chore(notification grouping): added custom grouping in signoz dispatcher (#8812) 2025-09-26 13:24:58 +00:00
Yunus M
8b485de584 chore: create a HOC to wrap components with ErrorBoundary (#9096)
* chore: create a HOC to wrap components with ErrorBoundary

* feat: move svg to public, use render from test-utils
2025-09-26 18:07:59 +05:30
Abhi kumar
d595dcc222 fix: added fix for passing activeLogId in query range in log context view (#9180)
* fix: added fix for passing activitylogId in query range in log context view

* chore: added tests
2025-09-26 13:17:38 +05:30
Ekansh Gupta
7ddaa84387 feat: add materialise ttl = 0 in set ttl v2 (#9189) 2025-09-26 05:44:13 +00:00
Niladri Adhikary
6d5f0adab9 fix: prevent panels with all queries disabled (#9093)
Signed-off-by: “niladrix719” <niladrix719@gmail.com>
2025-09-26 01:08:35 +00:00
primus-bot[bot]
2c19f0171f chore(release): bump to v0.96.1 (#9194)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2025-09-25 20:47:24 +05:30
Abhi kumar
9a5bcb6b64 revert: removed changes done for cursor position jump fix (#9193) 2025-09-25 13:57:05 +00:00
Aditya Singh
96cdf21a92 Fix: Opening logs link broken (Pref framework) (#9182)
* fix: logs popover content logic extracted out

* fix: logs popover content in live view

* fix: destory popover on close

* feat: add logs format tests

* feat: minor refactor

* feat: test case refactor

* feat: remove menu refs in logs live view
2025-09-25 13:44:05 +00:00
Yunus M
1aa5f5d0e1 fix: extra content passed by consuming component (#9191) 2025-09-25 13:30:40 +00:00
Vishal Sharma
6ac812b5af chore: change update workspace URL to upgrade guide (#9178)
* chore: change update workspace URL to upgrade guide

* chore: change upgrade workspace url
2025-09-25 16:38:38 +05:30
Vikrant Gupta
0b4831ca04 chore(authz): bump up openfga version (#9175)
* chore(authz): bump up openfga version

* chore(authz): bump up openfga version

* chore(authz): bump up openfga version

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-09-25 13:07:48 +05:30
primus-bot[bot]
340aa9ec21 chore(release): bump to v0.96.0 (#9179)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
Co-authored-by: Priyanshu Shrivastava <priyanshu@signoz.io>
2025-09-25 12:51:25 +05:30
Yunus M
5a47a4349b feat: hide feedback for non licensed users (#9176) 2025-09-25 12:40:21 +05:30
Ekansh Gupta
80f0c6dd92 feat: added cold storage in set ttl v2 method (#9151)
* feat: added cold storage in set ttl v2 method

* feat: standardised cold storage ttl to days

* feat: added coldstorage ttl in response structure of get api
2025-09-25 06:57:20 +00:00
Yunus M
c0acc69f87 fix: revert queryKey update to re-enable cancel run (#9105) 2025-09-25 12:05:02 +05:30
SagarRajput-7
9114b44c0e fix: correctly set and unset the stackbarchart value across panel types (#9158) 2025-09-24 22:37:31 +05:30
Vikrant Gupta
c68096152d chore(clickhouse): bump ch-go (#9169)
* fix(integration): fix tests

* fix(integration): fix tests

* chore(clickhouse): bump ch-go

---------

Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
2025-09-24 15:10:29 +05:30
Vikrant Gupta
4d8d0223e7 fix(integration): fix tests (#9168)
* fix(integration): fix tests

* fix(integration): fix tests
2025-09-24 14:54:43 +05:30
Yunus M
2f4b8f6f80 feat: standardise header to include share and feedback sections (#9037)
* feat: standardise header to include share and feedback sections

* feat: add unit test cases

* feat: handle click outside to close open modals

* fix: handle click outside to close modals

* chore: update event name and placeholder

* fix: test cases

* feat: show success / failure message on feedback submit, fix test cases

* feat: add test cases to check if toast messages are shown on feedback submit

* feat: address review comments

* feat: update test cases

---------

Co-authored-by: makeavish <makeavish786@gmail.com>
2025-09-24 11:52:37 +05:30
Amlan Kumar Nandy
a54c3a3d7f chore: add notification settings section to create alert (#9162) 2025-09-24 08:52:05 +05:30
Amlan Kumar Nandy
2c59c1196d chore: add evaluation settings section (#9134) 2025-09-23 15:36:40 +00:00
manika-signoz
73ff89a80a feat: revamp onboarding (#9068)
* feat: revamp onboarding, send list to mixpanel, join logic to convert to single string

* chore: props changes

* fix: allow user to proceed even if api fails

* chore: remove console.log

* chore: remove commented code

* chore: minor colour tweaks

* chore: resolve comments
2025-09-23 20:47:39 +05:30
Abhi kumar
b2dc2790d8 fix: invalid function name cumsum (#9161) 2025-09-23 14:37:44 +00:00
SagarRajput-7
dc8e4365f5 fix: fixed scroll reset issue when interacting with legends (#9065)
* fix: fixed scroll reset issue when interacting with legends

* fix: added test cases to ensure codes execution and req function are attached

* fix: added test cases

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-09-23 12:13:13 +00:00
Ekansh Gupta
eb38dd548a 3rd party sem conv fix (#8980)
* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: added native support for 1.26

* feat: added native support for 1.26

* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: fixed merge conflicts

* feat: fixed merge conflicts

* feat: added intermediate methods to fix response structure

* feat: fixed the errors on errors.newf
2025-09-23 10:55:59 +00:00
Abhi kumar
0ac5d97495 feat: Move 3rd party apis to QB V5 (#9042)
* feat: moved apis out and added proper types

* feat: intergrated new api in 3rd party monitoring

* feat: intergrated new API structure

* chore: fix for null pointer exception

* test: added test for formatDataForTable function

* chore: added placeholder prop in querysearch

* chore: added placeholder prop in querysearch

* feat: added hook for listoverview api
2025-09-23 16:15:05 +05:30
Abhi kumar
710f7740d3 fix: added fix for cursor jump in QB (#9140)
* fix: added fix for cursor jump in QB

* chore: minor cleanup

* feat: updating the query when the editor is getting out for focus or running the query

* test: added test for QuerySearch

* chore: updated variable name for QB interaction

* chore: updated PR review changes

* chore: removed non required comments
2025-09-23 13:06:52 +05:30
Amlan Kumar Nandy
a16ab114f5 chore: add evaluation cadence component for alerts v2 (#9131) 2025-09-22 20:12:59 +05:30
SagarRajput-7
84ae5b4ca9 fix: added dashboard route param, to allow trigger when new panel creation action happens (#9141) 2025-09-22 11:53:14 +05:30
Nityananda Gohain
a564fa9d28 fix: dont accept materialized key from payload (#9139)
* fix: dont accept materialized key from payload

* fix: use correct omit operator
2025-09-22 05:11:57 +00:00
aniketio-ctrl
7f4390f370 fix: Edit and patch rule functionality (#9125)
* fix: fixed edit and patch rule functionality

* fix: fixed edit and patch rule functionality

* fix: fixed edit and patch rule functionality

* fix: added patch rule test and rule mock store

* fix: removed schema version field

* fix: removed schema version field

* fix: added test cases for patch, create, edit

* fix: removed schema version field
2025-09-21 17:48:31 +05:30
Aditya Singh
c41ae00433 fix intermittent failing test (#9138)
* feat: context links processors

* feat: context variables hook added

* feat: add support for field variables

* feat: minor refactor

* feat: minor refactor

* feat: minor refactor

* feat: handle on save

* feat: minor refactor

* feat: snapshot update

* feat: revert qbv5

* feat: aggregation header val

* feat: fix header color

* feat: minor refactor

* feat: minor refactor

* feat: fix breaking changes from qb v5

* feat: change api for breakout opitons

* feat: minor refactor

* feat: minor refactor

* fix: added fix for extractquerypararms when value is string in multivalue operator

* feat: minor refactor

* feat: add back in breakout

* feat: minor refactor

* feat: add substitute var api call to decode vars

* feat: minor fix

* feat: optimize query value comparison in QueryBuilderV2

* feat: minor fix

* feat: minor fix

* feat: test fix

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable (#7944)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable

* feat: added test cases

* feat: added safety check

* feat: enabled url setting on first load itself

* feat: code refactor

* feat: cleared options query param when on dashboard list page

* feat: resolved conflicts

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* feat: minor refactor

* feat: added test cases

* feat: refactor

* feat: remove consoles

* feat: pass panel types to substitutevars

* feat: cross filtering init

* fix: added fix for query builder filters

* feat: cross filtering add set/unset/create functionality

* feat: test update

* fix: added migration to filter expression for crud operations of variable

* feat: format legend name according to existing format

* feat: breakout test init

* feat: breakout test match query

* feat: context links tests

* feat: minor refactor

* feat: show edit only if user has access

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: reverted dynamic variable url config changes (#8877)

* Revert "feat: changed query param name"

This reverts commit 62bee5f003.

* Revert "feat: added user-friendly format to dashboard variable url"

This reverts commit 6de8b1c2e8.

* feat: reverted url var changes

* feat: reverted url changed from usedashboardvarupdate hook

* feat: send empty array for widgetId

* feat: added type in the variables in query_range payload for dynamic

* feat: minor fixes

* fix: added fix for multivalue operator without brackets

* feat: minor fix

* feat: fix failing test

* feat: change revert

* test: added tests for querycontextUtils + querybuilderv2 utils

* fix: added fix for replacing filter with the new value

* fix: added fix for replacing filters + datetimepicker composite query

* test: fixed querybuilderv2 utils test

* feat: handle number dataType in filters

* feat: correct the variable addition to panel format for new qb expression

* feat: remove other queries in breakout

* feat: add metric to traces mapping

* feat: pass proper time range

* feat: update time range logic

* feat: value panel drilldown init

* feat: value panel drilldown init

* feat: enable context links in value panel

* feat: minor fix

* feat: update snapshot

* feat: hide breakout in value panel

* feat: add panel type to view mode

* feat: add support to change panel in breakouts

* feat: panel change for breakout logic added

* chore: fix style

* chore: show variables suggestion while creating context links

* chore: add timestamp to graphs

* chore: add timestamp to table panel

* chore: fix failing tests

* chore: fix infinite re-rendering due to queryRange

* chore: send appropriate time range when signal is metrics

* chore: show variables suggestion while creating context links

* chore: minor refactor

* chore: show trace details link if filter has trace_id

* chore: fix infinite render of table component

* chore: added tests for v2

* fix: context links set from dropdown

* chore: minor refactor

* chore: minor refactor

* chore: fix test

* chore: fix timerange for apm metrics

* fix: get correct timestamp for clicked data

* chore: comment out change to histogram on breakout by number

* chore: change panel type on panel type change in url

* chore: remove consoles

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: fix lint and test cases

* feat: fix typo

* feat: fixed test case

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: corrected the regex matcher for resolved titles

* feat: fixed test cases

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: added type in the variables in query_range payload for dynamic

* feat: correct the variable addition to panel format for new qb expression

* feat: added test cases for dynamic variable and add/remove panel feat

* feat: implemented where clause suggestion in new qb v5

* feat: added retries for dyn variable and fixed on-enter selection issue

* feat: added relatedValues and existing query in param related changes

* feat: sanitized data storage and removed duplicates

* fix: fixed typechecks

* feat: updated panel wait and refetch logic and ALL option selection

* feat: fixed variable tabel reordering issue

* feat: added empty name validation in variable creation

* feat: change value to searchtext in values API

* feat: added option for regex in the component, disabled for now

* feat: added beta and not rec. tag in variable tabs

* feat: added check to prevent api and updates calls with same payload

* feat: optimized localstorage for all selection in dynamic variable and updated __all__ case

* feat: resolved variable tables infinite loop update error

* feat: aded variable name auto-update based on attribute name entered for dynamic variables

* feat: modified only/all click behaviour and set all selection always true for dynamic variable

* feat: fix dropdown closing doesn't reset us back to our all available values when we have a search

* feat: handled all state distinction and carry forward in existing variables

* feat: trucate + n more tooltip content to 10

* feat: fixed infinite loop because of dependency of frequently changing object ref in var table

* feat: fixed inconsist search implementations

* feat: reverted only - all updated area implementation

* feat: added more space for search in multiselect component

* feat: checked for variable id instead of variable key for refetch

* feat: improved performance around multiselect component and added confirm modal for apply to all

* feat: rewrite functionality around add and remove panels

* feat: changed color for apply to all modal

* feat: added changes under flag to handle variable specific removal for removeKeysFromExpression func

* feat: added validation in variable edit panel

* chore: fix dynamic variable update in context menu to latest logic

* chore: minor fix

* chore: type fix

* fix: remove unwanted code

* fix: remove unwanted code

* fix: resolved pr comments

* fix: minor fix

* fix: fix tests

* fix: style fix

* fix: hide drilldown options in view mode for non-builder panels

* chore: add global uplot mock

* chore: query builder context update to all provider

* chore: add cursor rules init

* chore: useSafeNavigate mock added

* chore: more cleanups

* chore: remove react-router-v5 mock from setup

* chore: update cursorrules

* chore: add tests readme init

* chore: minor refactor

* fix: refetch quick filters on revisit to page

* fix: return expected response from queryFn and use as state

* fix: change getByRole to getByText for performant test

* chore: add sonner mock

* chore: mock revert

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
Co-authored-by: Abhi Kumar <ahrefabhi@gmail.com>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
Co-authored-by: SagarRajput-7 <sagar@signoz.io>
2025-09-20 11:18:04 +05:30
Amlan Kumar Nandy
9aacf7f2f5 chore: add context and time utils for usage in alerts (#9114) 2025-09-19 06:18:53 +00:00
SagarRajput-7
792d0f3db6 fix: removed staleTime and cacheTime from query client level (#9124)
* fix: removed staleTime for dashboard API, to fetch fresh data while switching between dashboards

* fix: removed query client level staleTime and cacheTime

* fix: adding dashbaordID to the query key

* fix: removed unnecessary query key
2025-09-19 10:55:19 +05:30
Aditya Singh
47e8a89dbe Fix: No quick filter found screen on navigating back from a diff screen (#9121)
* feat: minor refactor

* feat: change contextlinks data structure

* feat: context menu changes init

* feat: context menu hook refactor

* feat: context links processors

* feat: context variables hook added

* feat: add support for field variables

* feat: minor refactor

* feat: minor refactor

* feat: minor refactor

* feat: handle on save

* feat: minor refactor

* feat: snapshot update

* feat: revert qbv5

* feat: aggregation header val

* feat: fix header color

* feat: minor refactor

* feat: minor refactor

* feat: fix breaking changes from qb v5

* feat: change api for breakout opitons

* feat: minor refactor

* feat: minor refactor

* fix: added fix for extractquerypararms when value is string in multivalue operator

* feat: minor refactor

* feat: add back in breakout

* feat: minor refactor

* feat: add substitute var api call to decode vars

* feat: minor fix

* feat: optimize query value comparison in QueryBuilderV2

* feat: minor fix

* feat: minor fix

* feat: test fix

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable (#7944)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable

* feat: added test cases

* feat: added safety check

* feat: enabled url setting on first load itself

* feat: code refactor

* feat: cleared options query param when on dashboard list page

* feat: resolved conflicts

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* feat: minor refactor

* feat: added test cases

* feat: refactor

* feat: remove consoles

* feat: pass panel types to substitutevars

* feat: cross filtering init

* fix: added fix for query builder filters

* feat: cross filtering add set/unset/create functionality

* feat: test update

* fix: added migration to filter expression for crud operations of variable

* feat: format legend name according to existing format

* feat: breakout test init

* feat: breakout test match query

* feat: context links tests

* feat: minor refactor

* feat: show edit only if user has access

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: reverted dynamic variable url config changes (#8877)

* Revert "feat: changed query param name"

This reverts commit 62bee5f003.

* Revert "feat: added user-friendly format to dashboard variable url"

This reverts commit 6de8b1c2e8.

* feat: reverted url var changes

* feat: reverted url changed from usedashboardvarupdate hook

* feat: send empty array for widgetId

* feat: added type in the variables in query_range payload for dynamic

* feat: minor fixes

* fix: added fix for multivalue operator without brackets

* feat: minor fix

* feat: fix failing test

* feat: change revert

* test: added tests for querycontextUtils + querybuilderv2 utils

* fix: added fix for replacing filter with the new value

* fix: added fix for replacing filters + datetimepicker composite query

* test: fixed querybuilderv2 utils test

* feat: handle number dataType in filters

* feat: correct the variable addition to panel format for new qb expression

* feat: remove other queries in breakout

* feat: add metric to traces mapping

* feat: pass proper time range

* feat: update time range logic

* feat: value panel drilldown init

* feat: value panel drilldown init

* feat: enable context links in value panel

* feat: minor fix

* feat: update snapshot

* feat: hide breakout in value panel

* feat: add panel type to view mode

* feat: add support to change panel in breakouts

* feat: panel change for breakout logic added

* chore: fix style

* chore: show variables suggestion while creating context links

* chore: add timestamp to graphs

* chore: add timestamp to table panel

* chore: fix failing tests

* chore: fix infinite re-rendering due to queryRange

* chore: send appropriate time range when signal is metrics

* chore: show variables suggestion while creating context links

* chore: minor refactor

* chore: show trace details link if filter has trace_id

* chore: fix infinite render of table component

* chore: added tests for v2

* fix: context links set from dropdown

* chore: minor refactor

* chore: minor refactor

* chore: fix test

* chore: fix timerange for apm metrics

* fix: get correct timestamp for clicked data

* chore: comment out change to histogram on breakout by number

* chore: change panel type on panel type change in url

* chore: remove consoles

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: fix lint and test cases

* feat: fix typo

* feat: fixed test case

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: corrected the regex matcher for resolved titles

* feat: fixed test cases

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: added type in the variables in query_range payload for dynamic

* feat: correct the variable addition to panel format for new qb expression

* feat: added test cases for dynamic variable and add/remove panel feat

* feat: implemented where clause suggestion in new qb v5

* feat: added retries for dyn variable and fixed on-enter selection issue

* feat: added relatedValues and existing query in param related changes

* feat: sanitized data storage and removed duplicates

* fix: fixed typechecks

* feat: updated panel wait and refetch logic and ALL option selection

* feat: fixed variable tabel reordering issue

* feat: added empty name validation in variable creation

* feat: change value to searchtext in values API

* feat: added option for regex in the component, disabled for now

* feat: added beta and not rec. tag in variable tabs

* feat: added check to prevent api and updates calls with same payload

* feat: optimized localstorage for all selection in dynamic variable and updated __all__ case

* feat: resolved variable tables infinite loop update error

* feat: aded variable name auto-update based on attribute name entered for dynamic variables

* feat: modified only/all click behaviour and set all selection always true for dynamic variable

* feat: fix dropdown closing doesn't reset us back to our all available values when we have a search

* feat: handled all state distinction and carry forward in existing variables

* feat: trucate + n more tooltip content to 10

* feat: fixed infinite loop because of dependency of frequently changing object ref in var table

* feat: fixed inconsist search implementations

* feat: reverted only - all updated area implementation

* feat: added more space for search in multiselect component

* feat: checked for variable id instead of variable key for refetch

* feat: improved performance around multiselect component and added confirm modal for apply to all

* feat: rewrite functionality around add and remove panels

* feat: changed color for apply to all modal

* feat: added changes under flag to handle variable specific removal for removeKeysFromExpression func

* feat: added validation in variable edit panel

* chore: fix dynamic variable update in context menu to latest logic

* chore: minor fix

* chore: type fix

* fix: remove unwanted code

* fix: remove unwanted code

* fix: resolved pr comments

* fix: minor fix

* fix: fix tests

* fix: style fix

* fix: hide drilldown options in view mode for non-builder panels

* chore: add global uplot mock

* chore: query builder context update to all provider

* chore: add cursor rules init

* chore: useSafeNavigate mock added

* chore: more cleanups

* chore: remove react-router-v5 mock from setup

* chore: update cursorrules

* chore: add tests readme init

* chore: minor refactor

* fix: refetch quick filters on revisit to page

* fix: return expected response from queryFn and use as state

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
Co-authored-by: Abhi Kumar <ahrefabhi@gmail.com>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
Co-authored-by: SagarRajput-7 <sagar@signoz.io>
2025-09-18 12:51:54 +05:30
Aditya Singh
bced4774bb feat: frontend unit test suite setup (#9027)
* feat: update context link modal form init

* feat: add double way sync on urls and param

* feat: minor refactor

* feat: minor refactor

* feat: change contextlinks data structure

* feat: context menu changes init

* feat: context menu hook refactor

* feat: context links processors

* feat: context variables hook added

* feat: add support for field variables

* feat: minor refactor

* feat: minor refactor

* feat: minor refactor

* feat: handle on save

* feat: minor refactor

* feat: snapshot update

* feat: revert qbv5

* feat: aggregation header val

* feat: fix header color

* feat: minor refactor

* feat: minor refactor

* feat: fix breaking changes from qb v5

* feat: change api for breakout opitons

* feat: minor refactor

* feat: minor refactor

* fix: added fix for extractquerypararms when value is string in multivalue operator

* feat: minor refactor

* feat: add back in breakout

* feat: minor refactor

* feat: add substitute var api call to decode vars

* feat: minor fix

* feat: optimize query value comparison in QueryBuilderV2

* feat: minor fix

* feat: minor fix

* feat: test fix

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable (#7944)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable

* feat: added test cases

* feat: added safety check

* feat: enabled url setting on first load itself

* feat: code refactor

* feat: cleared options query param when on dashboard list page

* feat: resolved conflicts

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* feat: minor refactor

* feat: added test cases

* feat: refactor

* feat: remove consoles

* feat: pass panel types to substitutevars

* feat: cross filtering init

* fix: added fix for query builder filters

* feat: cross filtering add set/unset/create functionality

* feat: test update

* fix: added migration to filter expression for crud operations of variable

* feat: format legend name according to existing format

* feat: breakout test init

* feat: breakout test match query

* feat: context links tests

* feat: minor refactor

* feat: show edit only if user has access

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: reverted dynamic variable url config changes (#8877)

* Revert "feat: changed query param name"

This reverts commit 62bee5f003.

* Revert "feat: added user-friendly format to dashboard variable url"

This reverts commit 6de8b1c2e8.

* feat: reverted url var changes

* feat: reverted url changed from usedashboardvarupdate hook

* feat: send empty array for widgetId

* feat: added type in the variables in query_range payload for dynamic

* feat: minor fixes

* fix: added fix for multivalue operator without brackets

* feat: minor fix

* feat: fix failing test

* feat: change revert

* test: added tests for querycontextUtils + querybuilderv2 utils

* fix: added fix for replacing filter with the new value

* fix: added fix for replacing filters + datetimepicker composite query

* test: fixed querybuilderv2 utils test

* feat: handle number dataType in filters

* feat: correct the variable addition to panel format for new qb expression

* feat: remove other queries in breakout

* feat: add metric to traces mapping

* feat: pass proper time range

* feat: update time range logic

* feat: value panel drilldown init

* feat: value panel drilldown init

* feat: enable context links in value panel

* feat: minor fix

* feat: update snapshot

* feat: hide breakout in value panel

* feat: add panel type to view mode

* feat: add support to change panel in breakouts

* feat: panel change for breakout logic added

* chore: fix style

* chore: show variables suggestion while creating context links

* chore: add timestamp to graphs

* chore: add timestamp to table panel

* chore: fix failing tests

* chore: fix infinite re-rendering due to queryRange

* chore: send appropriate time range when signal is metrics

* chore: show variables suggestion while creating context links

* chore: minor refactor

* chore: show trace details link if filter has trace_id

* chore: fix infinite render of table component

* chore: added tests for v2

* fix: context links set from dropdown

* chore: minor refactor

* chore: minor refactor

* chore: fix test

* chore: fix timerange for apm metrics

* fix: get correct timestamp for clicked data

* chore: comment out change to histogram on breakout by number

* chore: change panel type on panel type change in url

* chore: remove consoles

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: fix lint and test cases

* feat: fix typo

* feat: fixed test case

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: corrected the regex matcher for resolved titles

* feat: fixed test cases

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: added type in the variables in query_range payload for dynamic

* feat: correct the variable addition to panel format for new qb expression

* feat: added test cases for dynamic variable and add/remove panel feat

* feat: implemented where clause suggestion in new qb v5

* feat: added retries for dyn variable and fixed on-enter selection issue

* feat: added relatedValues and existing query in param related changes

* feat: sanitized data storage and removed duplicates

* fix: fixed typechecks

* feat: updated panel wait and refetch logic and ALL option selection

* feat: fixed variable tabel reordering issue

* feat: added empty name validation in variable creation

* feat: change value to searchtext in values API

* feat: added option for regex in the component, disabled for now

* feat: added beta and not rec. tag in variable tabs

* feat: added check to prevent api and updates calls with same payload

* feat: optimized localstorage for all selection in dynamic variable and updated __all__ case

* feat: resolved variable tables infinite loop update error

* feat: aded variable name auto-update based on attribute name entered for dynamic variables

* feat: modified only/all click behaviour and set all selection always true for dynamic variable

* feat: fix dropdown closing doesn't reset us back to our all available values when we have a search

* feat: handled all state distinction and carry forward in existing variables

* feat: trucate + n more tooltip content to 10

* feat: fixed infinite loop because of dependency of frequently changing object ref in var table

* feat: fixed inconsist search implementations

* feat: reverted only - all updated area implementation

* feat: added more space for search in multiselect component

* feat: checked for variable id instead of variable key for refetch

* feat: improved performance around multiselect component and added confirm modal for apply to all

* feat: rewrite functionality around add and remove panels

* feat: changed color for apply to all modal

* feat: added changes under flag to handle variable specific removal for removeKeysFromExpression func

* feat: added validation in variable edit panel

* chore: fix dynamic variable update in context menu to latest logic

* chore: minor fix

* chore: type fix

* fix: remove unwanted code

* fix: remove unwanted code

* fix: resolved pr comments

* fix: minor fix

* fix: fix tests

* fix: style fix

* fix: hide drilldown options in view mode for non-builder panels

* chore: add global uplot mock

* chore: query builder context update to all provider

* chore: add cursor rules init

* chore: useSafeNavigate mock added

* chore: more cleanups

* chore: remove react-router-v5 mock from setup

* chore: update cursorrules

* chore: add tests readme init

* chore: minor refactor

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
Co-authored-by: Abhi Kumar <ahrefabhi@gmail.com>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
Co-authored-by: SagarRajput-7 <sagar@signoz.io>
2025-09-18 12:19:57 +05:30
Vikrant Gupta
0c25de9560 feat(authz): build authz service (#9064)
* feat(authz): define the domain layer

* feat(authz): added openfga schema and split the enterprise code

* feat(authz): revert http handler

* feat(authz): address comments

* feat(authz): address comments

* feat(authz): typo comments

* feat(authz): address review comments

* feat(authz): address review comments

* feat(authz): update the oss model

* feat(authz): update the sequential check
2025-09-17 21:35:11 +05:30
Shaheer Kochai
24307b48ff Fix: trace details bugfixes (#9116)
* fix: make the trace details sidebar scrollable

* fix: fix the long value overflowing trace details attributes

* fix: fix the layout issues in trace details v2

* Revert "fix: make the trace details sidebar scrollable"

This reverts commit 469022ed6a.

* fix: make the trace details sidebar scrollable

* fix: make the attribute value take 100% width

---------

Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
2025-09-17 19:28:34 +05:30
Piyush Singariya
0626a89412 Revert "fix: upgrading clickhouse-go (#8969)" (#9113)
This reverts commit 5cd775f2b2.
2025-09-17 16:31:02 +05:30
Piyush Singariya
5cd775f2b2 fix: upgrading clickhouse-go (#8969)
* test: upgrading clickhouse-go

* fix: go mod tidy

* fix: upgrade semconv

---------

Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
2025-09-17 14:11:18 +05:30
primus-bot[bot]
c9568be5d8 chore(release): bump to v0.95.0 (#9112)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2025-09-17 12:55:46 +05:30
Srikanth Chekuri
1c257f3e14 chore: populate default zero queries for metrics (#9103) 2025-09-17 07:05:38 +00:00
Amlan Kumar Nandy
ff8ac96d37 chore: fix edit alerts page crashing (#9025) 2025-09-17 06:14:25 +00:00
Tushar Vats
e8035b7dd2 Added Download Button to leverage exportRawData API (#9050)
This pull request introduces a new, customizable logs export feature in the Logs Explorer view, replacing the previous hardcoded download functionality. Users can now select export format, row limit, and which columns to include via a dedicated options menu. The implementation includes a new API integration for downloading export data, UI components for export options, and associated styling.
2025-09-17 03:37:44 +00:00
Srikanth Chekuri
cc77b829af chore: recognize dashboard view page for log_comment (#9084) 2025-09-16 19:43:54 +00:00
Nityananda Gohain
49306cbe3d fix: add trace perf improvement only when there is any filter (#9099) 2025-09-16 17:08:58 +00:00
Srikanth Chekuri
233a8e4cc3 chore: use wss usage for workloads listing (#9104) 2025-09-16 16:42:15 +00:00
Srikanth Chekuri
629378bbec chore: do not fail on non-existent signoz_ dbs (#9092) 2025-09-16 16:15:11 +00:00
Shaheer Kochai
d96073f478 feat: highlight the searched spans and dim other spans in trace details v2 (#9032) 2025-09-16 14:29:48 +00:00
Tushar Vats
ba8a49929a Converted format options menu to popover (#9094)
This pull request refactors the LogsFormatOptionsMenu component to use the Ant Design Popover for displaying format options, replacing the previous custom dropdown implementation. It also updates the related styles and removes now-unnecessary state and logic from parent components. The changes improve code maintainability, UI consistency, and simplify event handling.
2025-09-16 13:12:15 +00:00
Shaheer Kochai
a90904951e feat: make the trace details v2 sidebar resizable (#9034)
* feat: make the trace details v2 sidebar resizable

* chore: fix the failing test

* chore: improve the UI while resizing the sidebar
2025-09-16 17:20:32 +04:30
Shaheer Kochai
6c57735a81 feat: add support for span logs (#8857) 2025-09-16 11:22:15 +00:00
Shaheer Kochai
4851527840 fix: fix the incorrect matching spans count (#9033) 2025-09-16 08:56:47 +00:00
Niladri Adhikary
c5051128fa feat: warn when LIKE/ILIKE is used without any %/_ (#9098)
* feat: warn when LIKE/ILIKE is used without any %/_

Signed-off-by: “niladrix719” <niladrix719@gmail.com>
2025-09-16 06:23:26 +00:00
Yunus M
2acdd101d8 feat: clean up tooltip elements and add dependencies (#8815) 2025-09-16 11:08:51 +05:30
Amlan Kumar Nandy
39c2738ef9 feat: add alert condition component (#9008) 2025-09-15 21:22:07 +05:30
manika-signoz
d075ceecba chore: copy changes and minor fixes, onboarding hint (#9095) 2025-09-15 18:54:37 +05:30
aniketio-ctrl
ac81eab7bb chore: added cumulative window support (#8828)
* feat(multi-threshold): added multi threshold

* Update pkg/types/ruletypes/api_params.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* feat(multiple-threshold): added multiple thresholds

* Update pkg/types/ruletypes/alerting.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* feat(multiple-threshold): added multiple thresholds

* feat(cumulative-window): added cumulative window

* feat(multi-threshold): added recovery min points

* Update pkg/query-service/rules/threshold_rule.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* feat(multi-threshold): fixed log lines

* feat(multi-threshold): added severity as threshold name

* feat(cumulative-window): added cumulative window for alerts v2

* feat(multi-threshold): removed break to send multi threshold alerts

* feat(multi-threshold): removed break to send multi threshold alerts

* feat(cumulative-window): segregated json marshalling with evaluation logic

* feat(multi-threshold): corrected the test cases

* feat(cumulative-window): segregated json marshalling and evaluation logic

* feat(cumulative-window): segregated json marshalling and evaluation logic

* feat(multi-threshold): added segregation on json marshalling and actual threhsold logic

* feat(multi-threshold): added segregation on json marshalling and actual threhsold logic

* feat(cumulative-window): segregated json marshalling and evaluation logic

* feat(multi-threshold): added segregation on json marshalling and actual threhsold logic

* feat(cumulative-window): segregated json marshalling and evaluation logic

* feat(multi-threhsold): added error wrapper

* feat(multi-threhsold): added error wrapper

* feat(cumulative-window): segregated json marshalling and evaluation logic

* feat(multi-threhsold): added error wrapper

* Update pkg/types/ruletypes/threshold.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* feat(cumulative-window): segregated json marshalling and evaluation logic

* feat(multi-threshold): added validation and error propagation

* feat(multi-notification): removed pre defined labels from links of log and traces

* feat(multi-notification): removed pre defined labels from links of log and traces

* feat(multi-threshold): added json parser for gettable rule

* feat(multi-threshold): added json parser for gettable rule

* feat(multi-threshold): added json parser for gettable rule

* feat(multi-threshold): added umnarshaller for postable rule

* feat(multi-threshold): added umnarshaller for postable rule

* feat(cumulative-window): added validation check

* Update pkg/types/ruletypes/evaluation.go

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* feat(multi-threhsold): removed yaml support for alerts

* Update pkg/types/ruletypes/evaluation.go

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>

* Update pkg/types/ruletypes/evaluation.go

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>

* chore(cumulative-window): renamed funcitons

* chore(cumulative-window): removed naked errors

* chore(cumulative-window): added reset boundary condition tests

* chore(cumulative-window): added reset boundary condition tests

* chore(cumulative-window): sorted imports

* chore(cumulative-window): sorted imports

* chore(cumulative-window): sorted imports

* chore(cumulative-window): removed error from next window for

* chore(cumulative-window): removed error from next window for

* chore(cumulative-window): added case for timezone

* chore(cumulative-window): added validation for eval window

* chore(cumulative-window): updated api structure for cumulative window

* chore(cumulative-window): updated schedule enum

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-09-15 15:00:12 +05:30
Srikanth Chekuri
c982b1e76d chore: allow number segment, #, @, {} in key (#9082) 2025-09-14 22:14:42 +05:30
Srikanth Chekuri
252786deb6 chore: make qb v5 default (#9085) 2025-09-14 20:41:13 +05:30
Nityananda Gohain
38ca467d13 fix: trace perf - scan only required traces (#9072) 2025-09-14 13:12:48 +00:00
Nityananda Gohain
a686941880 fix: exception on resource filters with numeric values (#9028) 2025-09-14 13:00:16 +00:00
Nityananda Gohain
ae58915020 chore: support for json column in resources (#8376) 2025-09-14 18:18:39 +05:30
Vibhu Pandey
e9222ab3e0 docs(integration): add docs for writing integration tests (#9070) 2025-09-12 12:16:04 +00:00
aniketio-ctrl
d801fcee76 chore: add multiple thresholds support (#8816) 2025-09-12 13:11:54 +05:30
Vibhu Pandey
61acd946cc fix: enable dot metrics by default (#9061) 2025-09-11 17:59:55 +00:00
Abhi kumar
c477ec65da feat: added support for hasToken function in QB (#9058)
* feat: updated grammer to add hasToken function

* feat: added function constant
2025-09-11 12:55:13 +05:30
SagarRajput-7
9d999feabb feat: change Bar color opacity and make stacking as default (#9026) 2025-09-10 14:56:40 +00:00
Amlan Kumar Nandy
0658c561b9 chore: add query section to create alerts (#8991) 2025-09-10 14:43:54 +00:00
Abhi kumar
b1ea7eab70 chore: automatically show query addon when the value is present even after refresh (#9024)
* chore: automatically show query addon when the value is present even after refresh

* chore: minor cleanup

* test: added tests for queryAddon

* test: removed inputwithlabel mock
2025-09-10 19:53:06 +05:30
Vibhu Pandey
31e042adf7 feat(alertmanager): deprecate legacy alertmanager (#9046)
- Deprecate legacy alertmanager. Are the new alert improvements compatible with legacy? I don't think they are. More importantly, I don't think they should be. It will be a pain to manage it at both places.

- Improve msteamsv2 experience. I have taken alertmanager's upstream and merged it with our custom implementation. Note the use of `titleLink` field to propagate the url of the alert.

- Delete the private http server needed for alertmanager. It's cleanup as part of 1.
2025-09-10 08:28:13 +00:00
SagarRajput-7
f23000831c feat: update yarn lock file (#9055) 2025-09-10 05:52:57 +00:00
SagarRajput-7
f82e9b55f8 fix: logs explorer chart severity text bugfixes (#8731)
* fix: fixed severity color getting incorrectly assigned due to the response changed in v5 API

* fix: implement consistent severity variant colors across logs chart and indicator component

* chore: fix the failing tests

* chore: fix the failing check

---------

Co-authored-by: ahmadshaheer <ashaheerki@gmail.com>
Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
2025-09-10 05:21:06 +00:00
Shaheer Kochai
f91115948a feat: add support for span hover card in trace details v2 (#8930)
* feat: add support for span hover card in trace details v2

* chore: remove the unnecessary tooltip

---------

Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
2025-09-10 09:01:57 +04:30
SagarRajput-7
011b769d4d chore: added resolution brace-expansion to 2.0.2 to fix the vulnerability (#9049) 2025-09-09 14:53:36 +00:00
Abhi kumar
0129326a0b Fix: changelog modal spacing issue (#9048)
* fix: added fix for changelog modal styling issue

* chore: minor code cleanup

* chore: pr review changes

* chore: minor preetier fix
2025-09-09 18:44:33 +05:30
Tushar Vats
6c7275d355 Feat: API for exporting raw data (#8936)
This introduces a new Raw Data Export module to the codebase, enabling users to export raw log data via a dedicated API endpoint. The changes include the implementation of the module and handler, integration with existing infrastructure, configuration updates, and adjustments to tests and module wiring.
2025-09-09 17:04:40 +05:30
Vibhu Pandey
c83eaf3d50 chore: enable forbidigo and noerrors in depguard (#9047)
* chore: enable forbidgo

* chore: enable forbidgo
2025-09-09 15:44:27 +05:30
primus-bot[bot]
57013e1c4f chore(release): bump to v0.94.1 (#9045)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
2025-09-09 08:11:33 +00:00
Amlan Kumar Nandy
717efaf167 feat: base setup for new create alerts page (#8957) 2025-09-09 07:56:29 +00:00
Vikrant Gupta
6709b09646 fix(user): populate correct error message on client (#9043)
* fix(user): populate correct error message on client

* fix(user): populate correct error message on client
2025-09-09 13:05:07 +05:30
primus-bot[bot]
144e866afc chore(release): bump to v0.94.0 (#9040)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2025-09-08 22:26:06 +05:30
Nageshbansal
3f2763251a chore(clickhouse-upgrade): updates docker and docker swarm compose for clickhouse-25.5.6 (#9031) 2025-09-08 19:54:58 +05:30
Srikanth Chekuri
e67a576c07 chore: update CODEOWNERS (#9030) 2025-09-08 10:37:51 +00:00
aniketio-ctrl
c737a7e070 chore: enable alertmanager metrics collection with instrumentation::metrics (#9020)
* feat(alerts-v2): exposed alertmanager metrics for signozalertmanager

* feat(alerts-v2): exposed alertmanager metrics for signozalertmanager

* feat(alerts-v2): exposed alertmanager metrics for signozalertmanager

* feat(notification-routing): added custom meter provider

* feat(notification-routing): added custom meter provider

* feat(notification-routing): added custom meter provider

* feat(notification-routing): added custom meter provider

* feat(notification-routing): added org id label
2025-09-08 14:41:18 +05:30
Aditya Singh
74be8f5611 Interactive dashboards fix: hide drilldown features for non-query builder panels (#9023)
* feat: drilldown prop drilldowned

* feat: refactor code

* feat: update click plugin in uplot

* feat: lint fix

* feat: add search to breakout and other refactor

* feat: context menu - increase width and add overlay

* feat: add context links

* feat: context links init

* feat: context links init

* feat: context links init

* feat: update context link modal form init

* feat: add double way sync on urls and param

* feat: minor refactor

* feat: minor refactor

* feat: change contextlinks data structure

* feat: context menu changes init

* feat: context menu hook refactor

* feat: context links processors

* feat: context variables hook added

* feat: add support for field variables

* feat: minor refactor

* feat: minor refactor

* feat: minor refactor

* feat: handle on save

* feat: minor refactor

* feat: snapshot update

* feat: revert qbv5

* feat: aggregation header val

* feat: fix header color

* feat: minor refactor

* feat: minor refactor

* feat: fix breaking changes from qb v5

* feat: change api for breakout opitons

* feat: minor refactor

* feat: minor refactor

* fix: added fix for extractquerypararms when value is string in multivalue operator

* feat: minor refactor

* feat: add back in breakout

* feat: minor refactor

* feat: add substitute var api call to decode vars

* feat: minor fix

* feat: optimize query value comparison in QueryBuilderV2

* feat: minor fix

* feat: minor fix

* feat: test fix

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable (#7944)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable

* feat: added test cases

* feat: added safety check

* feat: enabled url setting on first load itself

* feat: code refactor

* feat: cleared options query param when on dashboard list page

* feat: resolved conflicts

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* feat: minor refactor

* feat: added test cases

* feat: refactor

* feat: remove consoles

* feat: pass panel types to substitutevars

* feat: cross filtering init

* fix: added fix for query builder filters

* feat: cross filtering add set/unset/create functionality

* feat: test update

* fix: added migration to filter expression for crud operations of variable

* feat: format legend name according to existing format

* feat: breakout test init

* feat: breakout test match query

* feat: context links tests

* feat: minor refactor

* feat: show edit only if user has access

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: reverted dynamic variable url config changes (#8877)

* Revert "feat: changed query param name"

This reverts commit 62bee5f003.

* Revert "feat: added user-friendly format to dashboard variable url"

This reverts commit 6de8b1c2e8.

* feat: reverted url var changes

* feat: reverted url changed from usedashboardvarupdate hook

* feat: send empty array for widgetId

* feat: added type in the variables in query_range payload for dynamic

* feat: minor fixes

* fix: added fix for multivalue operator without brackets

* feat: minor fix

* feat: fix failing test

* feat: change revert

* test: added tests for querycontextUtils + querybuilderv2 utils

* fix: added fix for replacing filter with the new value

* fix: added fix for replacing filters + datetimepicker composite query

* test: fixed querybuilderv2 utils test

* feat: handle number dataType in filters

* feat: correct the variable addition to panel format for new qb expression

* feat: remove other queries in breakout

* feat: add metric to traces mapping

* feat: pass proper time range

* feat: update time range logic

* feat: value panel drilldown init

* feat: value panel drilldown init

* feat: enable context links in value panel

* feat: minor fix

* feat: update snapshot

* feat: hide breakout in value panel

* feat: add panel type to view mode

* feat: add support to change panel in breakouts

* feat: panel change for breakout logic added

* chore: fix style

* chore: show variables suggestion while creating context links

* chore: add timestamp to graphs

* chore: add timestamp to table panel

* chore: fix failing tests

* chore: fix infinite re-rendering due to queryRange

* chore: send appropriate time range when signal is metrics

* chore: show variables suggestion while creating context links

* chore: minor refactor

* chore: show trace details link if filter has trace_id

* chore: fix infinite render of table component

* chore: added tests for v2

* fix: context links set from dropdown

* chore: minor refactor

* chore: minor refactor

* chore: fix test

* chore: fix timerange for apm metrics

* fix: get correct timestamp for clicked data

* chore: comment out change to histogram on breakout by number

* chore: change panel type on panel type change in url

* chore: remove consoles

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: fix lint and test cases

* feat: fix typo

* feat: fixed test case

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: corrected the regex matcher for resolved titles

* feat: fixed test cases

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: added type in the variables in query_range payload for dynamic

* feat: correct the variable addition to panel format for new qb expression

* feat: added test cases for dynamic variable and add/remove panel feat

* feat: implemented where clause suggestion in new qb v5

* feat: added retries for dyn variable and fixed on-enter selection issue

* feat: added relatedValues and existing query in param related changes

* feat: sanitized data storage and removed duplicates

* fix: fixed typechecks

* feat: updated panel wait and refetch logic and ALL option selection

* feat: fixed variable tabel reordering issue

* feat: added empty name validation in variable creation

* feat: change value to searchtext in values API

* feat: added option for regex in the component, disabled for now

* feat: added beta and not rec. tag in variable tabs

* feat: added check to prevent api and updates calls with same payload

* feat: optimized localstorage for all selection in dynamic variable and updated __all__ case

* feat: resolved variable tables infinite loop update error

* feat: aded variable name auto-update based on attribute name entered for dynamic variables

* feat: modified only/all click behaviour and set all selection always true for dynamic variable

* feat: fix dropdown closing doesn't reset us back to our all available values when we have a search

* feat: handled all state distinction and carry forward in existing variables

* feat: trucate + n more tooltip content to 10

* feat: fixed infinite loop because of dependency of frequently changing object ref in var table

* feat: fixed inconsist search implementations

* feat: reverted only - all updated area implementation

* feat: added more space for search in multiselect component

* feat: checked for variable id instead of variable key for refetch

* feat: improved performance around multiselect component and added confirm modal for apply to all

* feat: rewrite functionality around add and remove panels

* feat: changed color for apply to all modal

* feat: added changes under flag to handle variable specific removal for removeKeysFromExpression func

* feat: added validation in variable edit panel

* chore: fix dynamic variable update in context menu to latest logic

* chore: minor fix

* chore: type fix

* fix: remove unwanted code

* fix: remove unwanted code

* fix: resolved pr comments

* fix: minor fix

* fix: fix tests

* fix: style fix

* fix: hide drilldown options in view mode for non-builder panels

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
Co-authored-by: Abhi Kumar <ahrefabhi@gmail.com>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
Co-authored-by: SagarRajput-7 <sagar@signoz.io>
2025-09-07 09:29:33 +00:00
Srikanth Chekuri
1d3a8ecd66 fix(variable_replace_visitor): do not skip boolean value (#9021) 2025-09-07 08:40:11 +00:00
SagarRajput-7
0f5825a2b3 chore: product link and retires retry condition (#9018)
* feat: product link and retires retry condition

* feat: retry logic for 4xx and 5xx

* chore: tooltip editorial changes (#9019)

* chore: tooltip editorial changes

* feat: fixed lint error and styling

---------

Co-authored-by: SagarRajput-7 <sagar@signoz.io>

* feat: added test cases

* feat: fixed lint error

* feat: added product link in single select for dyn variables

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-09-07 13:36:12 +05:30
Aditya Singh
eee96503ff feat/interactive dashbaord v2 (#9011)
* feat: add drilldown options in uplot

* feat: add time range to timeseries, bar charts

* feat: remove unwanted code

* feat: minor refactor

* feat: drilldown prop drilldowned

* feat: refactor code

* feat: update click plugin in uplot

* feat: lint fix

* feat: add search to breakout and other refactor

* feat: context menu - increase width and add overlay

* feat: add context links

* feat: context links init

* feat: context links init

* feat: context links init

* feat: update context link modal form init

* feat: add double way sync on urls and param

* feat: minor refactor

* feat: minor refactor

* feat: change contextlinks data structure

* feat: context menu changes init

* feat: context menu hook refactor

* feat: context links processors

* feat: context variables hook added

* feat: add support for field variables

* feat: minor refactor

* feat: minor refactor

* feat: minor refactor

* feat: handle on save

* feat: minor refactor

* feat: snapshot update

* feat: revert qbv5

* feat: aggregation header val

* feat: fix header color

* feat: minor refactor

* feat: minor refactor

* feat: fix breaking changes from qb v5

* feat: change api for breakout opitons

* feat: minor refactor

* feat: minor refactor

* fix: added fix for extractquerypararms when value is string in multivalue operator

* feat: minor refactor

* feat: add back in breakout

* feat: minor refactor

* feat: add substitute var api call to decode vars

* feat: minor fix

* feat: optimize query value comparison in QueryBuilderV2

* feat: minor fix

* feat: minor fix

* feat: test fix

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable (#7944)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added variable in url and made dashboard sync around that and sharable

* feat: added test cases

* feat: added safety check

* feat: enabled url setting on first load itself

* feat: code refactor

* feat: cleared options query param when on dashboard list page

* feat: resolved conflicts

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* feat: minor refactor

* feat: added test cases

* feat: refactor

* feat: remove consoles

* feat: pass panel types to substitutevars

* feat: cross filtering init

* fix: added fix for query builder filters

* feat: cross filtering add set/unset/create functionality

* feat: test update

* fix: added migration to filter expression for crud operations of variable

* feat: format legend name according to existing format

* feat: breakout test init

* feat: breakout test match query

* feat: context links tests

* feat: minor refactor

* feat: show edit only if user has access

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: updated test case

* feat: corrected the regex matcher for resolved titles

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: reverted dynamic variable url config changes (#8877)

* Revert "feat: changed query param name"

This reverts commit 62bee5f003.

* Revert "feat: added user-friendly format to dashboard variable url"

This reverts commit 6de8b1c2e8.

* feat: reverted url var changes

* feat: reverted url changed from usedashboardvarupdate hook

* feat: send empty array for widgetId

* feat: added type in the variables in query_range payload for dynamic

* feat: minor fixes

* fix: added fix for multivalue operator without brackets

* feat: minor fix

* feat: fix failing test

* feat: change revert

* test: added tests for querycontextUtils + querybuilderv2 utils

* fix: added fix for replacing filter with the new value

* fix: added fix for replacing filters + datetimepicker composite query

* test: fixed querybuilderv2 utils test

* feat: handle number dataType in filters

* feat: correct the variable addition to panel format for new qb expression

* feat: remove other queries in breakout

* feat: add metric to traces mapping

* feat: pass proper time range

* feat: update time range logic

* feat: value panel drilldown init

* feat: value panel drilldown init

* feat: enable context links in value panel

* feat: minor fix

* feat: update snapshot

* feat: hide breakout in value panel

* feat: add panel type to view mode

* feat: add support to change panel in breakouts

* feat: panel change for breakout logic added

* chore: fix style

* chore: show variables suggestion while creating context links

* chore: add timestamp to graphs

* chore: add timestamp to table panel

* chore: fix failing tests

* chore: fix infinite re-rendering due to queryRange

* chore: send appropriate time range when signal is metrics

* chore: show variables suggestion while creating context links

* chore: minor refactor

* chore: show trace details link if filter has trace_id

* chore: fix infinite render of table component

* chore: added tests for v2

* fix: context links set from dropdown

* chore: minor refactor

* chore: minor refactor

* chore: fix test

* chore: fix timerange for apm metrics

* fix: get correct timestamp for clicked data

* chore: comment out change to histogram on breakout by number

* chore: change panel type on panel type change in url

* chore: remove consoles

* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: fix lint and test cases

* feat: fix typo

* feat: fixed test case

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: corrected the regex matcher for resolved titles

* feat: fixed test cases

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: added type in the variables in query_range payload for dynamic

* feat: correct the variable addition to panel format for new qb expression

* feat: added test cases for dynamic variable and add/remove panel feat

* feat: implemented where clause suggestion in new qb v5

* feat: added retries for dyn variable and fixed on-enter selection issue

* feat: added relatedValues and existing query in param related changes

* feat: sanitized data storage and removed duplicates

* fix: fixed typechecks

* feat: updated panel wait and refetch logic and ALL option selection

* feat: fixed variable tabel reordering issue

* feat: added empty name validation in variable creation

* feat: change value to searchtext in values API

* feat: added option for regex in the component, disabled for now

* feat: added beta and not rec. tag in variable tabs

* feat: added check to prevent api and updates calls with same payload

* feat: optimized localstorage for all selection in dynamic variable and updated __all__ case

* feat: resolved variable tables infinite loop update error

* feat: aded variable name auto-update based on attribute name entered for dynamic variables

* feat: modified only/all click behaviour and set all selection always true for dynamic variable

* feat: fix dropdown closing doesn't reset us back to our all available values when we have a search

* feat: handled all state distinction and carry forward in existing variables

* feat: trucate + n more tooltip content to 10

* feat: fixed infinite loop because of dependency of frequently changing object ref in var table

* feat: fixed inconsist search implementations

* feat: reverted only - all updated area implementation

* feat: added more space for search in multiselect component

* feat: checked for variable id instead of variable key for refetch

* feat: improved performance around multiselect component and added confirm modal for apply to all

* feat: rewrite functionality around add and remove panels

* feat: changed color for apply to all modal

* feat: added changes under flag to handle variable specific removal for removeKeysFromExpression func

* feat: added validation in variable edit panel

* chore: fix dynamic variable update in context menu to latest logic

* chore: minor fix

* chore: type fix

* fix: remove unwanted code

* fix: remove unwanted code

* fix: resolved pr comments

* fix: minor fix

* fix: fix tests

* fix: style fix

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
Co-authored-by: Abhi Kumar <ahrefabhi@gmail.com>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
Co-authored-by: SagarRajput-7 <sagar@signoz.io>
2025-09-07 11:50:35 +05:30
SagarRajput-7
7f925bd50e feat: added dynamic variable and other variable enhancements (#8873)
* feat: added dynamic variables creation flow (#7541)

* feat: added dynamic variables creation flow

* feat: added keys and value apis and hooks

* feat: added api and select component changes

* feat: added keys fetching and preview values

* feat: added dynamic variable to variable items

* feat: handled value persistence and tab switches

* feat: added default value and formed a schema for dyn-variables

* feat: added client and server side searches

* feat: corrected the initial load getfieldKey api

* feat: removed fetch on mount restriction

* feat: added dynamic variable to the dashboard details (#7755)

* feat: added dynamic variable to the dashboard details

* feat: added new component to existing variables

* feat: added enhancement to multiselect and select for dyn-variables

* feat: added refetch method between all dynamic-variables

* feat: correct error handling

* feat: correct error handling

* feat: enforced non-empty selectedvalues and default value

* feat: added client and server side searches

* feat: retry on error

* feat: correct error handling

* feat: handle defautl value in existing variables

* feat: lowercase the source for payload

* feat: fixed the incorrect assignment of active indices

* feat: improved handling of all option

* feat: improved the ALL option visuals

* feat: handled default value enforcement in existing variables

* feat: added unix time to values call

* feat: added incomplete data message and info to search

* feat: changed dashboard panel call handling with existing variables

* feat: adjusted the response type and data with the new API schema for values

* feat: code refactor

* feat: made dyn-variable option as the default

* feat: added test cases for dyn variable creation and completion

* feat: updated test cases

* feat: fix lint and test cases

* feat: fix typo

* feat: resolved comments and refactoring

* feat: added dynamic variable suggestion in where clause (#8875)

* feat: added dynamic variable suggestion in where clause

* feat: added test cases for hooks and api call functions

* feat: added test case for querybuildersearchv2 suggestion changes

* feat: code refactor

* feat: corrected the regex matcher for resolved titles

* feat: fixed test cases

* feat: added ability to add/remove variable filter to one or more existing panels (#8876)

* feat: added ability to add/remove variable filter to one or more existing panels

* feat: added widgetselector on variable creation

* feat: show labels in widget selector

* feat: added apply to all and variable removal logical

* feat: refectch only related and affected panels in case of dynamic variables

* feat: added button loader for apply-all

* feat: light-mode styles

* fix: added migration to filter expression for crud operations of variable

* feat: added type in the variables in query_range payload for dynamic

* feat: correct the variable addition to panel format for new qb expression

* feat: added test cases for dynamic variable and add/remove panel feat

* feat: implemented where clause suggestion in new qb v5

* feat: added retries for dyn variable and fixed on-enter selection issue

* feat: added relatedValues and existing query in param related changes

* feat: sanitized data storage and removed duplicates

* fix: fixed typechecks

* feat: updated panel wait and refetch logic and ALL option selection

* feat: fixed variable tabel reordering issue

* feat: added empty name validation in variable creation

* feat: change value to searchtext in values API

* feat: added option for regex in the component, disabled for now

* feat: added beta and not rec. tag in variable tabs

* feat: added check to prevent api and updates calls with same payload

* feat: optimized localstorage for all selection in dynamic variable and updated __all__ case

* feat: resolved variable tables infinite loop update error

* feat: aded variable name auto-update based on attribute name entered for dynamic variables

* feat: modified only/all click behaviour and set all selection always true for dynamic variable

* feat: fix dropdown closing doesn't reset us back to our all available values when we have a search

* feat: handled all state distinction and carry forward in existing variables

* feat: trucate + n more tooltip content to 10

* feat: fixed infinite loop because of dependency of frequently changing object ref in var table

* feat: fixed inconsist search implementations

* feat: reverted only - all updated area implementation

* feat: added more space for search in multiselect component

* feat: checked for variable id instead of variable key for refetch

* feat: improved performance around multiselect component and added confirm modal for apply to all

* feat: rewrite functionality around add and remove panels

* feat: changed color for apply to all modal

* feat: added changes under flag to handle variable specific removal for removeKeysFromExpression func

* feat: added validation in variable edit panel

* feat: fixed failing test cases due to recent logic change

* feat: added doc links in the dynamic variable feat

* feat: resolved comments and refactoring

* feat: resolved comments and refactoring

* feat: fixed test cases

* feat: fixed test cases
2025-09-06 14:48:45 +05:30
SagarRajput-7
1aa7e8b5d9 feat: added new component to existing variables (#7744)
* feat: added new component to existing variables

* feat: added multiselect component and solved dropdown closing on every selection

* feat: fixed incorrect all label

* feat: allow custom value

* feat: better styles

* feat: added maxtagcount placeholder

* feat: added onclear function

* feat: updated regex and handlings

* feat: updated regex and handlings

* feat: added enableall prop control

* feat: fix the rebase conflict

* feat: fixed comments

* feat: fix test case

* feat: added test cases for customMultiselect, customSelect and variableItem integration

* feat: added test cases for behaviour around values added and selections showed

* feat: refactored test cases

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-09-06 12:26:15 +05:30
Abhi kumar
bf704333b3 Feature/trace operators (#8869)
* feat: added traceoperator component and styles

* chore: minor style improvments

* feat: added conditions for traceoperator

* chore: minor UI fixes

* chore: type changes

* chore: added initialvalue for trace operators

* chore: Added changes to prepare request payload

* chore: fixed minor styles + minor ux fix

* feat: added span selector

* chore: added ui changes in the editor

* chore: removed traceoperations and reused queryoperations

* chore: minor changes in queryaddon and aggregation for support

* chore: added traceoperators in alerts

* chore: minor pr review change

* chore: linter fix

* fix: fixed minor ts issues

* fix: added limit support in traceoperator

* chore: minor fix in traceoperator styles

* chore: linting fix + icon changes

* chore: updated type

* chore: lint fixes

* feat: added changes for showing querynames in alerts

* feat: added trace operator grammer + antlr files

* feat: added traceoperator context util

* chore: added traceoperator validation function

* feat: added traceoperator editor

* feat: added queryname boost + operator constants

* fix: pr reviews

* chore: minor ui fix

* fix: updated grammer files

* test: added test for traceoperatorcontext

* chore: removed check for multiple queries in traceexplorer

* test: minor test fix

* test: fixed breaking mapQueryDataFromApi test

* chore: fixed logic to show trace operator

* chore: updated docs link

* chore: minor ui issue fix

* chore: changed trace operator query name

* chore: removed using spans from in trace opeartors

* fix: added fix for order by in trace opeartor

* feat: added changes related to saved in views in trace opeartor

* chore: added changes to keep indirect descendent operator at bottom

* chore: removed returnspansfrom field from traceoperator

* chore: updated file names + regenerated grammer

* chore: added beta tag in trace opeartor

* chore: pr review fixes

* Fix/tsc trace operator + tests (#8942)

* fix: added tsc fixes for trace operator

* chore: moved traceoperator utils

* test: added test for traceopertor util

* chore: tsc fix

* fix: fixed tsc issue

* Feat/trace operator dashboards (#8992)

* chore: added callout message for multiple queries without trace operators

* feat: added changes for supporting trace operators in dashboards

* chore: minor changes for list panel
2025-09-05 16:00:24 +00:00
Vibhu Pandey
f63f175a77 fix(binding): better error messages (#9010) 2025-09-05 15:47:19 +00:00
Ekansh Gupta
b6f5c053a0 feat: trace operators BE (#8293)
* feat: [draft] added implementation of trace operators

* feat: [draft] added implementation of trace operators

* feat: [draft] added implementation of trace operators

* feat: [draft] added implementation of trace operators

* feat: added implementation of trace operators

* feat: added implementation of trace operators

* feat: added implementation of trace operators

* feat: added implementation of trace operators

* feat: added implementation of trace operators

* feat: added implementation of trace operators

* feat: added implementation of trace operators

* feat: added implementation of trace operators

* feat: added implementation of trace operators

* feat: added implementation of trace operators

* feat: refactor trace operator

* feat: added postprocess

* feat: added postprocess

* feat: added postprocess

* feat: refactored the consume function

* feat: refactored the consume function

* feat: refactored the consume function

* feat: refactored the consume function

* feat: refactored the consume function

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: replaced info to debug logs

* feat: replaced info to debug logs

* feat: replaced info to debug logs

* feat: updated time series query

* feat: fixed merge conflicts

* feat: fixed merge conflicts

* feat: fixed merge conflicts

* feat: fixed merge conflicts

* feat: added deep copy in ranged queries

* feat: refactored fingerprinting

* feat: refactored fingerprinting

* feat: refactored fingerprinting

* feat: refactored fingerprinting

* feat: refactored fingerprinting

* feat: refactored fingerprinting

* feat: refactored fingerprinting

* feat: added comment for build all spans cte

* feat: added postprocess for timeseries and added limits to memory

* feat: fixed span count in trace view

* feat: fixed span count in trace view

* feat: fixed linting issues

* feat: fixed linting issues

* feat: fixed linting issues

* feat: fixed linting issues

---------

Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
2025-09-05 21:07:10 +05:30
Abhi kumar
abeadc7672 fix: backward compatibility for explorer in case of aggregateAttribute is not present (#9000) 2025-09-04 13:21:16 +00:00
SagarRajput-7
faadc60c74 fix: fixed table panels not sorting, due to mismatch in lookup (id vs name) for aggregations (#9002)
* fix: fixed table panels not sorting, due to mismatch in id for aggregations

* fix: added test cases for the sort and util for qbv5 aggregation
2025-09-04 18:44:38 +05:30
Vibhu Pandey
360e8309c8 feat(password): implement strong controls for password (#8983)
## 📄 Summary

implement strong controls for password. Now the password requirement is : 

password must be at least 12 characters long, should contain at least one uppercase letter [A-Z], one lowercase letter [a-z], one number [0-9], and one symbol
2025-09-04 17:22:28 +05:30
SagarRajput-7
27580b62ba fix: fixed full view height for table panel (#9004) 2025-09-04 10:40:30 +00:00
SagarRajput-7
bcd21cee74 fix: y axis unit not interactive in the panel edit mode (#9003) 2025-09-04 16:00:03 +05:30
Vikrant Gupta
2dbe0777f4 feat(authz): add openfga authz middleware (#8990)
* feat(authz): add openfga authz middleware

* feat(authz): update the auth context

* feat(authz): update the auth context

* feat(authz): update check request

* feat(authz): update check request

* feat(authz): add lifecycle tests

* feat(authz): add lifecycle tests

* feat(authz): add start-stop tests
2025-09-04 08:37:11 +00:00
SagarRajput-7
7602d863dd fix: fixed table panel no scroll issue due to style override from calender component (#8996) 2025-09-04 09:55:46 +05:30
Abhi kumar
68d9c6c3cc fix: added fix for incorrect query in dashboard on panel change to list panel (#8994)
* fix: added fix for incorrect query in dashboard on panel change to list issue

* test: added test for handleQueryChange

* chore: pr reviews
2025-09-03 14:00:22 +00:00
manika-signoz
10c6e1fac7 feat: add delete button to invite user flow (#8993) 2025-09-03 19:07:35 +05:30
Shaheer Kochai
3999a64c64 feat: add pinning functionality for span attributes (#8769)
* refactor: adjust the attribute pinning changes based on trace actionables latest changes

* feat: persist pinned attributes on the BE, fallback to local storage

* chore: overall improvement

* chore: fix the failing tests

* fix: make the changes w.r.t. pinned attributes preferences in in preference.go
2025-09-03 16:36:10 +04:30
Vikrant Gupta
729bfb31f1 feat(authz): implement the current usecases in openfga (#8982)
* feat(authz): implement the current usecases in openfga

* feat(authz): implement the current usecases in openfga

* feat(authz): extract out the schema and DI the same

* feat(authz): extract out the schema and DI the same
2025-09-02 11:00:47 +00:00
Nityananda Gohain
052fb8b703 fix: support canDefaultZero for logs and traces (#8973)
* fix: support canDefaultZero for logs and traces

* fix: remove increase

* fix: move changes to req.go

* fix: add tood

* fix: address comments
2025-09-02 09:54:55 +00:00
nikhilmantri0902
5d9247f591 Allow deletion of multiple panels for dashboard updates made with API key (#8903) 2025-09-02 09:30:22 +00:00
Yunus M
c0a9948146 feat: handle active log flow (#8946)
* feat: handle active log flow

* feat: show live logs in logs explorer view

* feat: enable live logs in logs explorer

* feat: show live time option only in logs list view

* chore: pass showLiveLogs as false in test cases

* fix: handle live logs data format to open in log details

* fix: use current query state for frequency chart in live logs view

* fix: encode filter expression, show live option only in list view
2025-09-02 11:05:52 +05:30
Vikrant Gupta
f3569a9a02 chore(meter): remove the meter data validity message for non cloud users (#8972) 2025-09-01 16:18:47 +00:00
Vikrant Gupta
0df1ed3b57 fix(dashboard): remove context from dashboard types (#8971) 2025-09-01 16:08:25 +00:00
Nityananda Gohain
d0132f11ae fix: update clickhouse-sql-parser (#8970) 2025-09-01 13:25:27 +00:00
Vikrant Gupta
f61e859901 feat(authz): embed openfga server (#8966)
* feat(access-control): embed openfga in signoz

* feat(authz): rename access control to authz

* feat(authz): fix codeowners and go mod tidy

* feat(authz): fix lint

* feat(authz): update go version and move convertor to instrumentation

* feat(authz): some more lint issues

* feat(authz): some more lint issues

* feat(authz): some more lint issues

* feat(authz): fix more lint issues

* feat(authz): make logger converter interface
2025-09-01 17:10:13 +05:30
Ekansh Gupta
4daec45d98 feat: added custom retention for logs api (#8513)
* feat: added custom retention for logs api

* feat: added custom retention for logs api

* feat: added implementation of trace operators

* feat: added validation checks for resource keys

* feat: added validation checks for resource keys

* feat: added validation checks for resource keys

* feat: added validation checks for resource keys

* feat: added validation checks for resource keys

* feat: added validation checks for resource keys

* feat: added validation checks for resource keys

* feat: added validation checks for resource keys

* feat: added default checks for custom retention

* feat: added default checks for custom retention

* feat: added default checks for custom retention

* feat: added change for ttl

* feat: v2 api supports both v1 and v2

* feat: v2 api supports both v1 and v2

* feat: v2 api supports both v1 and v2

* feat: v2 api supports both v1 and v2

* feat: added default_ttl in v1

* feat: added set logs ttl v1 from v2

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts
2025-09-01 07:30:57 +00:00
Ekansh Gupta
382d9d4a87 Revert "3rd party API sem conv fix (Supports >1.26) (#8822)" (#8954)
This reverts commit 396e0cdc2d.
2025-09-01 07:20:56 +00:00
Nityananda Gohain
87ce197631 fix: don't skip resource filter in main table for OR queries (#8958)
* fix: don't skip resource filter in main table for OR queries

* fix: dont skip resource table

* fix: make check case insensitive

* fix: iterate over token stream
2025-08-30 23:16:56 +05:30
aniketio-ctrl
3cc5a24a4b fix: return 404 status code if rule not found (#8940)
* fix(rule-alert-state): added 404 status code for invalid rules
2025-08-30 12:32:03 +05:30
Yunus M
9b8a892079 chore: use infinity table for logs column view (#8953) 2025-08-29 16:12:27 +05:30
Ekansh Gupta
396e0cdc2d 3rd party API sem conv fix (Supports >1.26) (#8822)
* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: added native support for 1.26

* feat: added native support for 1.26

* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: adding support for 1.26 semconv

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts

* feat: resolved conflicts
2025-08-29 09:50:16 +00:00
Abhi kumar
c838d7e2d4 chore: added api chagnes for logs retenetion v2 api (#8649)
* chore: added api chagnes for logs retenetion v2 api

* chore: added pr review fixes

* chore: minor fix

* feat: added api changes for setting retention period

* chore: pr review fixes

* chore: removed return statement

* fix: pr reviews

* fix: pr review
2025-08-29 15:10:15 +05:30
Nityananda Gohain
1a193fb1a9 fix: update email template for update role (#8610)
* fix: update email template for update role

* fix: remove space
2025-08-29 06:51:03 +00:00
Yunus M
88dff3f552 feat: minor ui updates (#8947) 2025-08-29 11:36:32 +05:30
Nityananda Gohain
5bb6d78c42 fix: remove isRoot and Entrypoint from selectfields (#8893)
* fix: remove isRoot and Entrypoint from selectfields

* fix: add comment

* fix: add comment

* fix: move logic to validation

* fix: remove requestType trace

* fix: update comment

* fix: update error message
2025-08-29 05:46:16 +00:00
Yunus M
369f77977d feat: use update props from data table component for better UX (#8950) 2025-08-29 10:06:43 +05:30
Vikrant Gupta
836605def5 feat(ingestion): add ingestion id to details (#8949) 2025-08-29 00:47:42 +05:30
Yunus M
cc80923265 feat: show timestamp in selected timezone format (#8948) 2025-08-29 00:18:05 +05:30
Yunus M
92e5986af2 feat: replace infinity list view component with data table component (#8904)
* feat: replace infinity list view component with data table component

* feat: remove duplicate hook calls

* chore: add @signozhq/table to transformIgnorePatterns

* feat: update test cases for frequency chart in logs explorer

* feat: address review comments , add sonner for notifications

* feat: address review comments
2025-08-28 19:03:29 +05:30
Abhi kumar
912a34da8d fix: added fix for context query not getting updated (#8941) 2025-08-28 15:14:18 +05:30
952 changed files with 77447 additions and 8654 deletions

View File

@@ -1,6 +1,6 @@
services:
clickhouse:
image: clickhouse/clickhouse-server:24.1.2-alpine
image: clickhouse/clickhouse-server:25.5.6
container_name: clickhouse
volumes:
- ${PWD}/fs/etc/clickhouse-server/config.d/config.xml:/etc/clickhouse-server/config.d/config.xml
@@ -23,6 +23,8 @@ services:
retries: 3
depends_on:
- zookeeper
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
zookeeper:
image: signoz/zookeeper:3.7.1
container_name: zookeeper
@@ -40,7 +42,7 @@ services:
timeout: 5s
retries: 3
schema-migrator-sync:
image: signoz/signoz-schema-migrator:v0.129.2
image: signoz/signoz-schema-migrator:v0.129.6
container_name: schema-migrator-sync
command:
- sync
@@ -53,7 +55,7 @@ services:
condition: service_healthy
restart: on-failure
schema-migrator-async:
image: signoz/signoz-schema-migrator:v0.129.2
image: signoz/signoz-schema-migrator:v0.129.6
container_name: schema-migrator-async
command:
- async

View File

@@ -1,6 +1,6 @@
services:
signoz-otel-collector:
image: signoz/signoz-otel-collector:v0.128.2
image: signoz/signoz-otel-collector:v0.129.6
container_name: signoz-otel-collector-dev
command:
- --config=/etc/otel-collector-config.yaml

43
.github/CODEOWNERS vendored
View File

@@ -5,6 +5,45 @@
/frontend/ @SigNoz/frontend @YounixM
/frontend/src/container/MetricsApplication @srikanthccv
/frontend/src/container/NewWidget/RightContainer/types.ts @srikanthccv
# Dashboard, Alert, Metrics, Service Map, Services
/frontend/src/container/ListOfDashboard/ @srikanthccv
/frontend/src/container/NewDashboard/ @srikanthccv
/frontend/src/pages/DashboardsListPage/ @srikanthccv
/frontend/src/pages/DashboardWidget/ @srikanthccv
/frontend/src/pages/NewDashboard/ @srikanthccv
/frontend/src/providers/Dashboard/ @srikanthccv
# Alerts
/frontend/src/container/AlertHistory/ @srikanthccv
/frontend/src/container/AllAlertChannels/ @srikanthccv
/frontend/src/container/AnomalyAlertEvaluationView/ @srikanthccv
/frontend/src/container/CreateAlertChannels/ @srikanthccv
/frontend/src/container/CreateAlertRule/ @srikanthccv
/frontend/src/container/EditAlertChannels/ @srikanthccv
/frontend/src/container/FormAlertChannels/ @srikanthccv
/frontend/src/container/FormAlertRules/ @srikanthccv
/frontend/src/container/ListAlertRules/ @srikanthccv
/frontend/src/container/TriggeredAlerts/ @srikanthccv
/frontend/src/pages/AlertChannelCreate/ @srikanthccv
/frontend/src/pages/AlertDetails/ @srikanthccv
/frontend/src/pages/AlertHistory/ @srikanthccv
/frontend/src/pages/AlertList/ @srikanthccv
/frontend/src/pages/CreateAlert/ @srikanthccv
/frontend/src/providers/Alert.tsx @srikanthccv
# Metrics
/frontend/src/container/MetricsExplorer/ @srikanthccv
/frontend/src/pages/MetricsApplication/ @srikanthccv
/frontend/src/pages/MetricsExplorer/ @srikanthccv
# Services and Service Map
/frontend/src/container/ServiceApplication/ @srikanthccv
/frontend/src/container/ServiceTable/ @srikanthccv
/frontend/src/pages/Services/ @srikanthccv
/frontend/src/pages/ServiceTopLevelOperations/ @srikanthccv
/frontend/src/container/Home/Services/ @srikanthccv
/deploy/ @SigNoz/devops
.github @SigNoz/devops
@@ -42,3 +81,7 @@
/pkg/telemetrymetadata/ @srikanthccv
/pkg/telemetrymetrics/ @srikanthccv
/pkg/telemetrytraces/ @srikanthccv
# AuthN / AuthZ Owners
/pkg/authz/ @vikrantgupta25 @grandwizard28

View File

@@ -62,7 +62,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
GO_VERSION: 1.24
GO_NAME: signoz-community
GO_INPUT_ARTIFACT_CACHE_KEY: community-jsbuild-${{ github.sha }}
GO_INPUT_ARTIFACT_PATH: frontend/build

View File

@@ -93,7 +93,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
GO_VERSION: 1.24
GO_INPUT_ARTIFACT_CACHE_KEY: enterprise-jsbuild-${{ github.sha }}
GO_INPUT_ARTIFACT_PATH: frontend/build
GO_BUILD_CONTEXT: ./cmd/enterprise

View File

@@ -92,7 +92,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
GO_VERSION: 1.24
GO_INPUT_ARTIFACT_CACHE_KEY: staging-jsbuild-${{ github.sha }}
GO_INPUT_ARTIFACT_PATH: frontend/build
GO_BUILD_CONTEXT: ./cmd/enterprise

View File

@@ -18,7 +18,7 @@ jobs:
with:
PRIMUS_REF: main
GO_TEST_CONTEXT: ./...
GO_VERSION: 1.23
GO_VERSION: 1.24
fmt:
if: |
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
@@ -27,7 +27,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
GO_VERSION: 1.24
lint:
if: |
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
@@ -36,7 +36,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
GO_VERSION: 1.24
deps:
if: |
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
@@ -45,7 +45,7 @@ jobs:
secrets: inherit
with:
PRIMUS_REF: main
GO_VERSION: 1.23
GO_VERSION: 1.24
build:
if: |
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
@@ -57,7 +57,7 @@ jobs:
- name: go-install
uses: actions/setup-go@v5
with:
go-version: "1.23"
go-version: "1.24"
- name: qemu-install
uses: docker/setup-qemu-action@v3
- name: aarch64-install

View File

@@ -58,7 +58,7 @@ jobs:
- name: setup-go
uses: actions/setup-go@v5
with:
go-version: "1.23"
go-version: "1.24"
- name: cross-compilation-tools
if: matrix.os == 'ubuntu-latest'
run: |
@@ -122,7 +122,7 @@ jobs:
- name: setup-go
uses: actions/setup-go@v5
with:
go-version: "1.23"
go-version: "1.24"
# copy the caches from build
- name: get-sha

View File

@@ -72,7 +72,7 @@ jobs:
- name: setup-go
uses: actions/setup-go@v5
with:
go-version: "1.23"
go-version: "1.24"
- name: cross-compilation-tools
if: matrix.os == 'ubuntu-latest'
run: |
@@ -135,7 +135,7 @@ jobs:
- name: setup-go
uses: actions/setup-go@v5
with:
go-version: "1.23"
go-version: "1.24"
# copy the caches from build
- name: get-sha

View File

@@ -21,10 +21,9 @@ jobs:
- postgres
- sqlite
clickhouse-version:
- 24.1.2-alpine
- 25.5.6
schema-migrator-version:
- v0.128.1
- v0.129.6
postgres-version:
- 15
if: |

6
.gitignore vendored
View File

@@ -86,6 +86,8 @@ queries.active
.devenv/**/tmp/**
.qodo
.dev
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
@@ -228,4 +230,6 @@ poetry.toml
# LSP config files
pyrightconfig.json
# End of https://www.toptal.com/developers/gitignore/api/python
# cursor files
frontend/.cursor/

View File

@@ -8,6 +8,7 @@ linters:
- depguard
- iface
- unparam
- forbidigo
linters-settings:
sloglint:
@@ -24,6 +25,10 @@ linters-settings:
deny:
- pkg: "go.uber.org/zap"
desc: "Do not use zap logger. Use slog instead."
noerrors:
deny:
- pkg: "errors"
desc: "Do not use errors package. Use github.com/SigNoz/signoz/pkg/errors instead."
iface:
enable:
- identical

View File

@@ -78,4 +78,5 @@ Need assistance? Join our Slack community:
- Set up your [development environment](docs/contributing/development.md)
- Deploy and observe [SigNoz in action with OpenTelemetry Demo Application](docs/otel-demo-docs.md)
- Explore the [SigNoz Community Advocate Program](ADVOCATE.md), which recognises contributors who support the community, share their expertise, and help shape SigNoz's future.
- Explore the [SigNoz Community Advocate Program](ADVOCATE.md), which recognises contributors who support the community, share their expertise, and help shape SigNoz's future.
- Write [integration tests](docs/contributing/go/integration.md)

View File

@@ -32,7 +32,7 @@ func registerServer(parentCmd *cobra.Command, logger *slog.Logger) {
Short: "Run the SigNoz server",
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
RunE: func(currCmd *cobra.Command, args []string) error {
config, err := cmd.NewSigNozConfig(currCmd.Context(), flags)
config, err := cmd.NewSigNozConfig(currCmd.Context(), logger, flags)
if err != nil {
return err
}

View File

@@ -2,7 +2,6 @@ package cmd
import (
"context"
"fmt"
"log/slog"
"os"
@@ -12,9 +11,10 @@ import (
"github.com/SigNoz/signoz/pkg/signoz"
)
func NewSigNozConfig(ctx context.Context, flags signoz.DeprecatedFlags) (signoz.Config, error) {
func NewSigNozConfig(ctx context.Context, logger *slog.Logger, flags signoz.DeprecatedFlags) (signoz.Config, error) {
config, err := signoz.NewConfig(
ctx,
logger,
config.ResolverConfig{
Uris: []string{"env:"},
ProviderFactories: []config.ProviderFactory{
@@ -31,14 +31,10 @@ func NewSigNozConfig(ctx context.Context, flags signoz.DeprecatedFlags) (signoz.
return config, nil
}
func NewJWTSecret(_ context.Context, _ *slog.Logger) string {
func NewJWTSecret(ctx context.Context, logger *slog.Logger) string {
jwtSecret := os.Getenv("SIGNOZ_JWT_SECRET")
if len(jwtSecret) == 0 {
fmt.Println("🚨 CRITICAL SECURITY ISSUE: No JWT secret key specified!")
fmt.Println("SIGNOZ_JWT_SECRET environment variable is not set. This has dire consequences for the security of the application.")
fmt.Println("Without a JWT secret, user sessions are vulnerable to tampering and unauthorized access.")
fmt.Println("Please set the SIGNOZ_JWT_SECRET environment variable immediately.")
fmt.Println("For more information, please refer to https://github.com/SigNoz/signoz/issues/8400.")
logger.ErrorContext(ctx, "🚨 CRITICAL SECURITY ISSUE: No JWT secret key specified!", "error", "SIGNOZ_JWT_SECRET environment variable is not set. This has dire consequences for the security of the application. Without a JWT secret, user sessions are vulnerable to tampering and unauthorized access. Please set the SIGNOZ_JWT_SECRET environment variable immediately. For more information, please refer to https://github.com/SigNoz/signoz/issues/8400.")
}
return jwtSecret

View File

@@ -2,10 +2,11 @@ FROM node:18-bullseye AS build
WORKDIR /opt/
COPY ./frontend/ ./
ENV NODE_OPTIONS=--max-old-space-size=8192
RUN CI=1 yarn install
RUN CI=1 yarn build
FROM golang:1.23-bullseye
FROM golang:1.24-bullseye
ARG OS="linux"
ARG TARGETARCH

View File

@@ -35,7 +35,7 @@ func registerServer(parentCmd *cobra.Command, logger *slog.Logger) {
Short: "Run the SigNoz server",
FParseErrWhitelist: cobra.FParseErrWhitelist{UnknownFlags: true},
RunE: func(currCmd *cobra.Command, args []string) error {
config, err := cmd.NewSigNozConfig(currCmd.Context(), flags)
config, err := cmd.NewSigNozConfig(currCmd.Context(), logger, flags)
if err != nil {
return err
}

View File

@@ -137,10 +137,7 @@ prometheus:
##################### Alertmanager #####################
alertmanager:
# Specifies the alertmanager provider to use.
provider: legacy
legacy:
# The API URL (with prefix) of the legacy Alertmanager instance.
api_url: http://localhost:9093/api
provider: signoz
signoz:
# The poll interval for periodically syncing the alertmanager with the config in the store.
poll_interval: 1m

View File

@@ -11,7 +11,7 @@ x-common: &common
max-file: "3"
x-clickhouse-defaults: &clickhouse-defaults
!!merge <<: *common
image: clickhouse/clickhouse-server:24.1.2-alpine
image: clickhouse/clickhouse-server:25.5.6
tty: true
deploy:
labels:
@@ -37,6 +37,8 @@ x-clickhouse-defaults: &clickhouse-defaults
nofile:
soft: 262144
hard: 262144
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
x-zookeeper-defaults: &zookeeper-defaults
!!merge <<: *common
image: signoz/zookeeper:3.7.1
@@ -63,7 +65,7 @@ x-db-depend: &db-depend
services:
init-clickhouse:
!!merge <<: *common
image: clickhouse/clickhouse-server:24.1.2-alpine
image: clickhouse/clickhouse-server:25.5.6
command:
- bash
- -c
@@ -174,7 +176,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.93.0
image: signoz/signoz:v0.96.1
command:
- --config=/root/config/prometheus.yml
ports:
@@ -207,7 +209,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.129.2
image: signoz/signoz-otel-collector:v0.129.6
command:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
@@ -231,7 +233,7 @@ services:
- signoz
schema-migrator:
!!merge <<: *common
image: signoz/signoz-schema-migrator:v0.129.2
image: signoz/signoz-schema-migrator:v0.129.6
deploy:
restart_policy:
condition: on-failure

View File

@@ -11,7 +11,7 @@ x-common: &common
max-file: "3"
x-clickhouse-defaults: &clickhouse-defaults
!!merge <<: *common
image: clickhouse/clickhouse-server:24.1.2-alpine
image: clickhouse/clickhouse-server:25.5.6
tty: true
deploy:
labels:
@@ -36,6 +36,8 @@ x-clickhouse-defaults: &clickhouse-defaults
nofile:
soft: 262144
hard: 262144
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
x-zookeeper-defaults: &zookeeper-defaults
!!merge <<: *common
image: signoz/zookeeper:3.7.1
@@ -60,7 +62,7 @@ x-db-depend: &db-depend
services:
init-clickhouse:
!!merge <<: *common
image: clickhouse/clickhouse-server:24.1.2-alpine
image: clickhouse/clickhouse-server:25.5.6
command:
- bash
- -c
@@ -115,7 +117,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.93.0
image: signoz/signoz:v0.96.1
command:
- --config=/root/config/prometheus.yml
ports:
@@ -148,7 +150,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.129.2
image: signoz/signoz-otel-collector:v0.129.6
command:
- --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml
@@ -174,7 +176,7 @@ services:
- signoz
schema-migrator:
!!merge <<: *common
image: signoz/signoz-schema-migrator:v0.129.2
image: signoz/signoz-schema-migrator:v0.129.6
deploy:
restart_policy:
condition: on-failure

View File

@@ -10,7 +10,7 @@ x-common: &common
x-clickhouse-defaults: &clickhouse-defaults
!!merge <<: *common
# addding non LTS version due to this fix https://github.com/ClickHouse/ClickHouse/commit/32caf8716352f45c1b617274c7508c86b7d1afab
image: clickhouse/clickhouse-server:24.1.2-alpine
image: clickhouse/clickhouse-server:25.5.6
tty: true
labels:
signoz.io/scrape: "true"
@@ -40,6 +40,8 @@ x-clickhouse-defaults: &clickhouse-defaults
nofile:
soft: 262144
hard: 262144
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
x-zookeeper-defaults: &zookeeper-defaults
!!merge <<: *common
image: signoz/zookeeper:3.7.1
@@ -65,7 +67,7 @@ x-db-depend: &db-depend
services:
init-clickhouse:
!!merge <<: *common
image: clickhouse/clickhouse-server:24.1.2-alpine
image: clickhouse/clickhouse-server:25.5.6
container_name: signoz-init-clickhouse
command:
- bash
@@ -177,7 +179,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.93.0}
image: signoz/signoz:${VERSION:-v0.96.1}
container_name: signoz
command:
- --config=/root/config/prometheus.yml
@@ -211,7 +213,7 @@ services:
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.129.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.129.6}
container_name: signoz-otel-collector
command:
- --config=/etc/otel-collector-config.yaml
@@ -237,7 +239,7 @@ services:
condition: service_healthy
schema-migrator-sync:
!!merge <<: *common
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.6}
container_name: schema-migrator-sync
command:
- sync
@@ -248,7 +250,7 @@ services:
condition: service_healthy
schema-migrator-async:
!!merge <<: *db-depend
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.6}
container_name: schema-migrator-async
command:
- async

View File

@@ -9,8 +9,7 @@ x-common: &common
max-file: "3"
x-clickhouse-defaults: &clickhouse-defaults
!!merge <<: *common
# addding non LTS version due to this fix https://github.com/ClickHouse/ClickHouse/commit/32caf8716352f45c1b617274c7508c86b7d1afab
image: clickhouse/clickhouse-server:24.1.2-alpine
image: clickhouse/clickhouse-server:25.5.6
tty: true
labels:
signoz.io/scrape: "true"
@@ -36,6 +35,8 @@ x-clickhouse-defaults: &clickhouse-defaults
nofile:
soft: 262144
hard: 262144
environment:
- CLICKHOUSE_SKIP_USER_SETUP=1
x-zookeeper-defaults: &zookeeper-defaults
!!merge <<: *common
image: signoz/zookeeper:3.7.1
@@ -61,7 +62,7 @@ x-db-depend: &db-depend
services:
init-clickhouse:
!!merge <<: *common
image: clickhouse/clickhouse-server:24.1.2-alpine
image: clickhouse/clickhouse-server:25.5.6
container_name: signoz-init-clickhouse
command:
- bash
@@ -110,7 +111,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.93.0}
image: signoz/signoz:${VERSION:-v0.96.1}
container_name: signoz
command:
- --config=/root/config/prometheus.yml
@@ -143,7 +144,7 @@ services:
retries: 3
otel-collector:
!!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.129.2}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.129.6}
container_name: signoz-otel-collector
command:
- --config=/etc/otel-collector-config.yaml
@@ -165,7 +166,7 @@ services:
condition: service_healthy
schema-migrator-sync:
!!merge <<: *common
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.6}
container_name: schema-migrator-sync
command:
- sync
@@ -177,7 +178,7 @@ services:
restart: on-failure
schema-migrator-async:
!!merge <<: *db-depend
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.2}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.129.6}
container_name: schema-migrator-async
command:
- async

View File

@@ -0,0 +1,213 @@
# Integration Tests
SigNoz uses integration tests to verify that different components work together correctly in a real environment. These tests run against actual services (ClickHouse, PostgreSQL, etc.) to ensure end-to-end functionality.
## How to set up the integration test environment?
### Prerequisites
Before running integration tests, ensure you have the following installed:
- Python 3.13+
- Poetry (for dependency management)
- Docker (for containerized services)
### Initial Setup
1. Navigate to the integration tests directory:
```bash
cd tests/integration
```
2. Install dependencies using Poetry:
```bash
poetry install --no-root
```
### Starting the Test Environment
To spin up all the containers necessary for writing integration tests and keep them running:
```bash
poetry run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/setup.py::test_setup
```
This command will:
- Start all required services (ClickHouse, PostgreSQL, Zookeeper, etc.)
- Keep containers running due to the `--reuse` flag
- Verify that the setup is working correctly
### Stopping the Test Environment
When you're done writing integration tests, clean up the environment:
```bash
poetry run pytest --basetemp=./tmp/ -vv --teardown -s src/bootstrap/setup.py::test_teardown
```
This will destroy the running integration test setup and clean up resources.
## Understanding the Integration Test Framework
Python and pytest form the foundation of the integration testing framework. Testcontainers are used to spin up disposable integration environments. Wiremock is used to spin up **test doubles** of other services.
- **Why Python/pytest?** It's expressive, low-boilerplate, and has powerful fixture capabilities that make integration testing straightforward. Extensive libraries for HTTP requests, JSON handling, and data analysis (numpy) make it easier to test APIs and verify data
- **Why testcontainers?** They let us spin up isolated dependencies that match our production environment without complex setup.
- **Why wiremock?** Well maintained, documented and extensible.
```
.
├── conftest.py
├── fixtures
│ ├── __init__.py
│ ├── auth.py
│ ├── clickhouse.py
│ ├── fs.py
│ ├── http.py
│ ├── migrator.py
│ ├── network.py
│ ├── postgres.py
│ ├── signoz.py
│ ├── sql.py
│ ├── sqlite.py
│ ├── types.py
│ └── zookeeper.py
├── poetry.lock
├── pyproject.toml
└── src
└── bootstrap
├── __init__.py
├── a_database.py
├── b_register.py
└── c_license.py
```
Each test suite follows some important principles:
1. **Organization**: Test suites live under `src/` in self-contained packages. Fixtures (a pytest concept) live inside `fixtures/`.
2. **Execution Order**: Files are prefixed with `a_`, `b_`, `c_` to ensure sequential execution.
3. **Time Constraints**: Each suite should complete in under 10 minutes (setup takes ~4 mins).
### Test Suite Design
Test suites should target functional domains or subsystems within SigNoz. When designing a test suite, consider these principles:
- **Functional Cohesion**: Group tests around a specific capability or service boundary
- **Data Flow**: Follow the path of data through related components
- **Change Patterns**: Components frequently modified together should be tested together
The exact boundaries for modules are intentionally flexible, allowing teams to define logical groupings based on their specific context and knowledge of the system.
Eg: The **bootstrap** integration test suite validates core system functionality:
- Database initialization
- Version check
Other test suites can be **pipelines, auth, querier.**
## How to write an integration test?
Now start writing an integration test. Create a new file `src/bootstrap/e_version.py` and paste the following:
```python
import requests
from fixtures import types
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
def test_version(signoz: types.SigNoz) -> None:
response = requests.get(signoz.self.host_config.get("/api/v1/version"), timeout=2)
logger.info(response)
```
We have written a simple test which calls the `version` endpoint of the container in step 1. In **order to just run this function, run the following command:**
```bash
poetry run pytest --basetemp=./tmp/ -vv --reuse src/bootstrap/e_version.py::test_version
```
> Note: The `--reuse` flag is used to reuse the environment if it is already running. Always use this flag when writing and running integration tests. If you don't use this flag, the environment will be destroyed and recreated every time you run the test.
Here's another example of how to write a more comprehensive integration test:
```python
from http import HTTPStatus
import requests
from fixtures import types
from fixtures.logger import setup_logger
logger = setup_logger(__name__)
def test_user_registration(signoz: types.SigNoz) -> None:
"""Test user registration functionality."""
response = requests.post(
signoz.self.host_configs["8080"].get("/api/v1/register"),
json={
"name": "testuser",
"orgId": "",
"orgName": "test.org",
"email": "test@example.com",
"password": "password123Z$",
},
timeout=2,
)
assert response.status_code == HTTPStatus.OK
assert response.json()["setupCompleted"] is True
```
## How to run integration tests?
### Running All Tests
```bash
poetry run pytest --basetemp=./tmp/ -vv --reuse src/
```
### Running Specific Test Categories
```bash
poetry run pytest --basetemp=./tmp/ -vv --reuse src/<suite>
# Run querier tests
poetry run pytest --basetemp=./tmp/ -vv --reuse src/querier/
# Run auth tests
poetry run pytest --basetemp=./tmp/ -vv --reuse src/auth/
```
### Running Individual Tests
```bash
poetry run pytest --basetemp=./tmp/ -vv --reuse src/<suite>/<file>.py::test_name
# Run test_register in file a_register.py in auth suite
poetry run pytest --basetemp=./tmp/ -vv --reuse src/auth/a_register.py::test_register
```
## How to configure different options for integration tests?
Tests can be configured using pytest options:
- `--sqlstore-provider` - Choose database provider (default: postgres)
- `--postgres-version` - PostgreSQL version (default: 15)
- `--clickhouse-version` - ClickHouse version (default: 25.5.6)
- `--zookeeper-version` - Zookeeper version (default: 3.7.1)
Example:
```bash
poetry run pytest --basetemp=./tmp/ -vv --reuse --sqlstore-provider=postgres --postgres-version=14 src/auth/
```
## What should I remember?
- **Always use the `--reuse` flag** when setting up the environment to keep containers running
- **Use the `--teardown` flag** when cleaning up to avoid resource leaks
- **Follow the naming convention** with alphabetical prefixes for test execution order
- **Use proper timeouts** in HTTP requests to avoid hanging tests
- **Clean up test data** between tests to avoid interference
- **Use descriptive test names** that clearly indicate what is being tested
- **Leverage fixtures** for common setup and authentication
- **Test both success and failure scenarios** to ensure robust functionality

View File

@@ -0,0 +1,40 @@
module base
type organisation
relations
define read: [user, role#assignee]
define update: [user, role#assignee]
type user
relations
define read: [user, role#assignee]
define update: [user, role#assignee]
define delete: [user, role#assignee]
type anonymous
type role
relations
define assignee: [user]
define read: [user, role#assignee]
define update: [user, role#assignee]
define delete: [user, role#assignee]
type resources
relations
define create: [user, role#assignee]
define list: [user, role#assignee]
type resource
relations
define read: [user, anonymous, role#assignee]
define update: [user, role#assignee]
define delete: [user, role#assignee]
define block: [user, role#assignee]
type telemetry
relations
define read: [user, anonymous, role#assignee]

View File

@@ -0,0 +1,29 @@
package openfgaschema
import (
"context"
_ "embed"
"github.com/SigNoz/signoz/pkg/authz"
openfgapkgtransformer "github.com/openfga/language/pkg/go/transformer"
)
var (
//go:embed base.fga
baseDSL string
)
type schema struct{}
func NewSchema() authz.Schema {
return &schema{}
}
func (schema *schema) Get(ctx context.Context) []openfgapkgtransformer.ModuleFile {
return []openfgapkgtransformer.ModuleFile{
{
Name: "base.fga",
Contents: baseDSL,
},
}
}

132
ee/http/middleware/authz.go Normal file
View File

@@ -0,0 +1,132 @@
package middleware
import (
"log/slog"
"net/http"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/gorilla/mux"
)
const (
authzDeniedMessage string = "::AUTHZ-DENIED::"
)
type AuthZ struct {
logger *slog.Logger
authzService authz.AuthZ
}
func NewAuthZ(logger *slog.Logger) *AuthZ {
if logger == nil {
panic("cannot build authz middleware, logger is empty")
}
return &AuthZ{logger: logger}
}
func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
if err != nil {
render.Error(rw, err)
return
}
if err := claims.IsViewer(); err != nil {
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
render.Error(rw, err)
return
}
next(rw, req)
})
}
func (middleware *AuthZ) EditAccess(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
if err != nil {
render.Error(rw, err)
return
}
if err := claims.IsEditor(); err != nil {
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
render.Error(rw, err)
return
}
next(rw, req)
})
}
func (middleware *AuthZ) AdminAccess(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
if err != nil {
render.Error(rw, err)
return
}
if err := claims.IsAdmin(); err != nil {
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
render.Error(rw, err)
return
}
next(rw, req)
})
}
func (middleware *AuthZ) SelfAccess(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
if err != nil {
render.Error(rw, err)
return
}
id := mux.Vars(req)["id"]
if err := claims.IsSelfAccess(id); err != nil {
middleware.logger.WarnContext(req.Context(), authzDeniedMessage, "claims", claims)
render.Error(rw, err)
return
}
next(rw, req)
})
}
func (middleware *AuthZ) OpenAccess(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
next(rw, req)
})
}
// Check middleware accepts the relation, typeable, parentTypeable (for direct access + group relations) and a callback function to derive selector and parentSelectors on per request basis.
func (middleware *AuthZ) Check(next http.HandlerFunc, relation authtypes.Relation, translation authtypes.Relation, typeable authtypes.Typeable, cb authtypes.SelectorCallbackFn) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
claims, err := authtypes.ClaimsFromContext(req.Context())
if err != nil {
render.Error(rw, err)
return
}
selector, err := cb(req.Context(), claims)
if err != nil {
render.Error(rw, err)
return
}
err = middleware.authzService.CheckWithTupleCreation(req.Context(), claims, relation, typeable, selector)
if err != nil {
render.Error(rw, err)
return
}
next(rw, req)
})
}

View File

@@ -13,11 +13,11 @@ import (
"github.com/SigNoz/signoz/ee/query-service/constants"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/modules/user"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/google/uuid"
"github.com/gorilla/mux"
"go.uber.org/zap"
)
@@ -192,14 +192,14 @@ func (ah *APIHandler) getOrCreateCloudIntegrationUser(
))
}
password, err := types.NewFactorPassword(uuid.NewString())
password := types.MustGenerateFactorPassword(newUser.ID.StringValue())
integrationUser, err := ah.Signoz.Modules.User.CreateUserWithPassword(ctx, newUser, password)
err = ah.Signoz.Modules.User.CreateUser(ctx, newUser, user.WithFactorPassword(password))
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration user: %w", err))
}
return integrationUser, nil
return newUser, nil
}
func getIngestionUrlAndSigNozAPIUrl(ctx context.Context, licenseKey string) (

View File

@@ -8,6 +8,8 @@ import (
"net/http"
_ "net/http/pprof" // http profiler
"github.com/SigNoz/signoz/pkg/ruler/rulestore/sqlrulestore"
"github.com/gorilla/handlers"
"github.com/SigNoz/signoz/ee/query-service/app/api"
@@ -44,19 +46,6 @@ import (
"go.uber.org/zap"
)
type ServerOptions struct {
Config signoz.Config
SigNoz *signoz.SigNoz
HTTPHostPort string
PrivateHostPort string
PreferSpanMetrics bool
FluxInterval string
FluxIntervalForTraceDetail string
Cluster string
GatewayUrl string
Jwt *authtypes.JWT
}
// Server runs HTTP, Mux and a grpc server
type Server struct {
config signoz.Config
@@ -69,11 +58,6 @@ type Server struct {
httpServer *http.Server
httpHostPort string
// private http
privateConn net.Listener
privateHTTP *http.Server
privateHostPort string
opampServer *opamp.Server
// Usage manager
@@ -183,7 +167,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz, jwt *authtypes.JWT)
jwt: jwt,
ruleManager: rm,
httpHostPort: baseconst.HTTPHostPort,
privateHostPort: baseconst.PrivateHostPort,
unavailableChannel: make(chan healthcheck.Status),
usageManager: usageManager,
}
@@ -196,13 +179,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz, jwt *authtypes.JWT)
s.httpServer = httpServer
privateServer, err := s.createPrivateServer(apiHandler)
if err != nil {
return nil, err
}
s.privateHTTP = privateServer
s.opampServer = opamp.InitializeServer(
&opAmpModel.AllAgents, agentConfMgr, signoz.Instrumentation,
)
@@ -215,36 +191,6 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
return s.unavailableChannel
}
func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server, error) {
r := baseapp.NewRouter()
r.Use(middleware.NewAuth(s.jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}, s.signoz.Sharder, s.signoz.Instrumentation.Logger()).Wrap)
r.Use(middleware.NewAPIKey(s.signoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.signoz.Instrumentation.Logger(), s.signoz.Sharder).Wrap)
r.Use(middleware.NewTimeout(s.signoz.Instrumentation.Logger(),
s.config.APIServer.Timeout.ExcludedRoutes,
s.config.APIServer.Timeout.Default,
s.config.APIServer.Timeout.Max,
).Wrap)
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
apiHandler.RegisterPrivateRoutes(r)
c := cors.New(cors.Options{
//todo(amol): find out a way to add exact domain or
// ip here for alert manager
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "DELETE", "POST", "PUT", "PATCH"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "SIGNOZ-API-KEY", "X-SIGNOZ-QUERY-ID", "Sec-WebSocket-Protocol"},
})
handler := c.Handler(r)
handler = handlers.CompressHandler(handler)
return &http.Server{
Handler: handler,
}, nil
}
func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*http.Server, error) {
r := baseapp.NewRouter()
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger())
@@ -310,19 +256,6 @@ func (s *Server) initListeners() error {
zap.L().Info(fmt.Sprintf("Query server started listening on %s...", s.httpHostPort))
// listen on private port to support internal services
privateHostPort := s.privateHostPort
if privateHostPort == "" {
return fmt.Errorf("baseconst.PrivateHostPort is required")
}
s.privateConn, err = net.Listen("tcp", privateHostPort)
if err != nil {
return err
}
zap.L().Info(fmt.Sprintf("Query server started listening on private port %s...", s.privateHostPort))
return nil
}
@@ -361,26 +294,6 @@ func (s *Server) Start(ctx context.Context) error {
}
}()
var privatePort int
if port, err := utils.GetPort(s.privateConn.Addr()); err == nil {
privatePort = port
}
go func() {
zap.L().Info("Starting Private HTTP server", zap.Int("port", privatePort), zap.String("addr", s.privateHostPort))
switch err := s.privateHTTP.Serve(s.privateConn); err {
case nil, http.ErrServerClosed, cmux.ErrListenerClosed:
// normal exit, nothing to do
zap.L().Info("private http server closed")
default:
zap.L().Error("Could not start private HTTP server", zap.Error(err))
}
s.unavailableChannel <- healthcheck.Unavailable
}()
go func() {
zap.L().Info("Starting OpAmp Websocket server", zap.String("addr", baseconst.OpAmpWsEndpoint))
err := s.opampServer.Start(baseconst.OpAmpWsEndpoint)
@@ -400,12 +313,6 @@ func (s *Server) Stop(ctx context.Context) error {
}
}
if s.privateHTTP != nil {
if err := s.privateHTTP.Shutdown(ctx); err != nil {
return err
}
}
s.opampServer.Stop()
if s.ruleManager != nil {
@@ -418,17 +325,9 @@ func (s *Server) Stop(ctx context.Context) error {
return nil
}
func makeRulesManager(
ch baseint.Reader,
cache cache.Cache,
alertmanager alertmanager.Alertmanager,
sqlstore sqlstore.SQLStore,
telemetryStore telemetrystore.TelemetryStore,
prometheus prometheus.Prometheus,
orgGetter organization.Getter,
querier querier.Querier,
logger *slog.Logger,
) (*baserules.Manager, error) {
func makeRulesManager(ch baseint.Reader, cache cache.Cache, alertmanager alertmanager.Alertmanager, sqlstore sqlstore.SQLStore, telemetryStore telemetrystore.TelemetryStore, prometheus prometheus.Prometheus, orgGetter organization.Getter, querier querier.Querier, logger *slog.Logger) (*baserules.Manager, error) {
ruleStore := sqlrulestore.NewRuleStore(sqlstore)
maintenanceStore := sqlrulestore.NewMaintenanceStore(sqlstore)
// create manager opts
managerOpts := &baserules.ManagerOptions{
TelemetryStore: telemetryStore,
@@ -443,8 +342,10 @@ func makeRulesManager(
PrepareTaskFunc: rules.PrepareTaskFunc,
PrepareTestRuleFunc: rules.TestNotification,
Alertmanager: alertmanager,
SQLStore: sqlstore,
OrgGetter: orgGetter,
RuleStore: ruleStore,
MaintenanceStore: maintenanceStore,
SqlStore: sqlstore,
}
// create Manager

View File

@@ -40,7 +40,7 @@ var IsDotMetricsEnabled = false
var IsPreferSpanMetrics = false
func init() {
if GetOrDefaultEnv(DotMetricsEnabled, "false") == "true" {
if GetOrDefaultEnv(DotMetricsEnabled, "true") == "true" {
IsDotMetricsEnabled = true
}

View File

@@ -1,7 +1,7 @@
package model
import (
"fmt"
"errors"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
)
@@ -57,7 +57,7 @@ func Unauthorized(err error) *ApiError {
func BadRequestStr(s string) *ApiError {
return &ApiError{
Typ: basemodel.ErrorBadData,
Err: fmt.Errorf(s),
Err: errors.New(s),
}
}
@@ -73,7 +73,7 @@ func InternalError(err error) *ApiError {
func InternalErrorStr(s string) *ApiError {
return &ApiError{
Typ: basemodel.ErrorInternal,
Err: fmt.Errorf(s),
Err: errors.New(s),
}
}

View File

@@ -35,7 +35,6 @@ import (
anomalyV2 "github.com/SigNoz/signoz/ee/anomaly"
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
yaml "gopkg.in/yaml.v2"
)
const (
@@ -167,16 +166,9 @@ func (r *AnomalyRule) prepareQueryRange(ctx context.Context, ts time.Time) (*v3.
ctx, "prepare query range request v4", "ts", ts.UnixMilli(), "eval_window", r.EvalWindow().Milliseconds(), "eval_delay", r.EvalDelay().Milliseconds(),
)
start := ts.Add(-time.Duration(r.EvalWindow())).UnixMilli()
end := ts.UnixMilli()
if r.EvalDelay() > 0 {
start = start - int64(r.EvalDelay().Milliseconds())
end = end - int64(r.EvalDelay().Milliseconds())
}
// round to minute otherwise we could potentially miss data
start = start - (start % (60 * 1000))
end = end - (end % (60 * 1000))
st, en := r.Timestamps(ts)
start := st.UnixMilli()
end := en.UnixMilli()
compositeQuery := r.Condition().CompositeQuery
@@ -253,10 +245,17 @@ func (r *AnomalyRule) buildAndRunQuery(ctx context.Context, orgID valuer.UUID, t
r.logger.InfoContext(ctx, "anomaly scores", "scores", string(scoresJSON))
for _, series := range queryResult.AnomalyScores {
smpl, shouldAlert := r.ShouldAlert(*series)
if shouldAlert {
resultVector = append(resultVector, smpl)
if r.Condition() != nil && r.Condition().RequireMinPoints {
if len(series.Points) < r.Condition().RequiredNumPoints {
r.logger.InfoContext(ctx, "not enough data points to evaluate series, skipping", "ruleid", r.ID(), "numPoints", len(series.Points), "requiredPoints", r.Condition().RequiredNumPoints)
continue
}
}
results, err := r.Threshold.ShouldAlert(*series, r.Unit())
if err != nil {
return nil, err
}
resultVector = append(resultVector, results...)
}
return resultVector, nil
}
@@ -296,10 +295,17 @@ func (r *AnomalyRule) buildAndRunQueryV5(ctx context.Context, orgID valuer.UUID,
r.logger.InfoContext(ctx, "anomaly scores", "scores", string(scoresJSON))
for _, series := range queryResult.AnomalyScores {
smpl, shouldAlert := r.ShouldAlert(*series)
if shouldAlert {
resultVector = append(resultVector, smpl)
if r.Condition().RequireMinPoints {
if len(series.Points) < r.Condition().RequiredNumPoints {
r.logger.InfoContext(ctx, "not enough data points to evaluate series, skipping", "ruleid", r.ID(), "numPoints", len(series.Points), "requiredPoints", r.Condition().RequiredNumPoints)
continue
}
}
results, err := r.Threshold.ShouldAlert(*series, r.Unit())
if err != nil {
return nil, err
}
resultVector = append(resultVector, results...)
}
return resultVector, nil
}
@@ -330,14 +336,19 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (interface{}, erro
resultFPs := map[uint64]struct{}{}
var alerts = make(map[uint64]*ruletypes.Alert, len(res))
ruleReceivers := r.Threshold.GetRuleReceivers()
ruleReceiverMap := make(map[string][]string)
for _, value := range ruleReceivers {
ruleReceiverMap[value.Name] = value.Channels
}
for _, smpl := range res {
l := make(map[string]string, len(smpl.Metric))
for _, lbl := range smpl.Metric {
l[lbl.Name] = lbl.Value
}
value := valueFormatter.Format(smpl.V, r.Unit())
threshold := valueFormatter.Format(r.TargetVal(), r.Unit())
threshold := valueFormatter.Format(smpl.Target, smpl.TargetUnit)
r.logger.DebugContext(ctx, "Alert template data for rule", "rule_name", r.Name(), "formatter", valueFormatter.Name(), "value", value, "threshold", threshold)
tmplData := ruletypes.AlertTemplateData(l, value, threshold)
@@ -381,6 +392,7 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (interface{}, erro
}
if smpl.IsMissing {
lb.Set(labels.AlertNameLabel, "[No data] "+r.Name())
lb.Set(labels.NoDataLabel, "true")
}
lbs := lb.Labels()
@@ -401,13 +413,12 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (interface{}, erro
State: model.StatePending,
Value: smpl.V,
GeneratorURL: r.GeneratorURL(),
Receivers: r.PreferredChannels(),
Receivers: ruleReceiverMap[lbs.Map()[ruletypes.LabelThresholdName]],
Missing: smpl.IsMissing,
}
}
r.logger.InfoContext(ctx, "number of alerts found", "rule_name", r.Name(), "alerts_count", len(alerts))
// alerts[h] is ready, add or update active list now
for h, a := range alerts {
// Check whether we already have alerting state for the identifying label set.
@@ -416,7 +427,9 @@ func (r *AnomalyRule) Eval(ctx context.Context, ts time.Time) (interface{}, erro
alert.Value = a.Value
alert.Annotations = a.Annotations
alert.Receivers = r.PreferredChannels()
if v, ok := alert.Labels.Map()[ruletypes.LabelThresholdName]; ok {
alert.Receivers = ruleReceiverMap[v]
}
continue
}
@@ -499,7 +512,7 @@ func (r *AnomalyRule) String() string {
PreferredChannels: r.PreferredChannels(),
}
byt, err := yaml.Marshal(ar)
byt, err := json.Marshal(ar)
if err != nil {
return fmt.Sprintf("error marshaling alerting rule: %s", err.Error())
}

View File

@@ -3,8 +3,10 @@ package rules
import (
"context"
"fmt"
"time"
"github.com/SigNoz/signoz/pkg/errors"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/query-service/utils/labels"
@@ -20,6 +22,10 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
var task baserules.Task
ruleId := baserules.RuleIdFromTaskName(opts.TaskName)
evaluation, err := opts.Rule.Evaluation.GetEvaluation()
if err != nil {
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "evaluation is invalid: %v", err)
}
if opts.Rule.RuleType == ruletypes.RuleTypeThreshold {
// create a threshold rule
tr, err := baserules.NewThresholdRule(
@@ -40,7 +46,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
rules = append(rules, tr)
// create ch rule task for evalution
task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
} else if opts.Rule.RuleType == ruletypes.RuleTypeProm {
@@ -62,7 +68,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
rules = append(rules, pr)
// create promql rule task for evalution
task = newTask(baserules.TaskTypeProm, opts.TaskName, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
task = newTask(baserules.TaskTypeProm, opts.TaskName, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
} else if opts.Rule.RuleType == ruletypes.RuleTypeAnomaly {
// create anomaly rule
@@ -84,7 +90,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
rules = append(rules, ar)
// create anomaly rule task for evalution
task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(opts.Rule.Frequency), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
task = newTask(baserules.TaskTypeCh, opts.TaskName, time.Duration(evaluation.GetFrequency()), rules, opts.ManagerOpts, opts.NotifyFunc, opts.MaintenanceStore, opts.OrgID)
} else {
return nil, fmt.Errorf("unsupported rule type %s. Supported types: %s, %s", opts.Rule.RuleType, ruletypes.RuleTypeProm, ruletypes.RuleTypeThreshold)
@@ -120,7 +126,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
if parsedRule.RuleType == ruletypes.RuleTypeThreshold {
// add special labels for test alerts
parsedRule.Annotations[labels.AlertSummaryLabel] = fmt.Sprintf("The rule threshold is set to %.4f, and the observed metric value is {{$value}}.", *parsedRule.RuleCondition.Target)
parsedRule.Labels[labels.RuleSourceLabel] = ""
parsedRule.Labels[labels.AlertRuleIdLabel] = ""

484
frontend/.cursorrules Normal file
View File

@@ -0,0 +1,484 @@
# Persona
You are an expert developer with deep knowledge of Jest, React Testing Library, MSW, and TypeScript, tasked with creating unit tests for this repository.
# Auto-detect TypeScript Usage
Check for TypeScript in the project through tsconfig.json or package.json dependencies.
Adjust syntax based on this detection.
# TypeScript Type Safety for Jest Tests
**CRITICAL**: All Jest tests MUST be fully type-safe with proper TypeScript types.
**Type Safety Requirements:**
- Use proper TypeScript interfaces for all mock data
- Type all Jest mock functions with `jest.MockedFunction<T>`
- Use generic types for React components and hooks
- Define proper return types for mock functions
- Use `as const` for literal types when needed
- Avoid `any` type use proper typing instead
# Unit Testing Focus
Focus on critical functionality (business logic, utility functions, component behavior)
Mock dependencies (API calls, external modules) before imports
Test multiple data scenarios (valid inputs, invalid inputs, edge cases)
Write maintainable tests with descriptive names grouped in describe blocks
# Global vs Local Mocks
**Use Global Mocks for:**
- High-frequency dependencies (20+ test files)
- Core infrastructure (react-router-dom, react-query, antd)
- Standard implementations across the app
- Browser APIs (ResizeObserver, matchMedia, localStorage)
- Utility libraries (date-fns, lodash)
**Use Local Mocks for:**
- Business logic dependencies (5-15 test files)
- Test-specific behavior (different data per test)
- API endpoints with specific responses
- Domain-specific components
- Error scenarios and edge cases
**Global Mock Files Available (from jest.config.ts):**
- `uplot` → `__mocks__/uplotMock.ts`
# Repo-specific Testing Conventions
## Imports
Always import from our harness:
```ts
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
```
For API mocks:
```ts
import { server, rest } from 'mocks-server/server';
```
Do not import directly from `@testing-library/react`.
## Router
Use the router built into render:
```ts
render(<Page />, undefined, { initialRoute: '/traces-explorer' });
```
Only mock `useLocation` / `useParams` if the test depends on them.
## Hook Mocks
Pattern:
```ts
import useFoo from 'hooks/useFoo';
jest.mock('hooks/useFoo');
const mockUseFoo = jest.mocked(useFoo);
mockUseFoo.mockReturnValue(/* minimal shape */ as any);
```
Prefer helpers (`rqSuccess`, `rqLoading`, `rqError`) for React Query results.
## MSW
Global MSW server runs automatically.
Override per-test:
```ts
server.use(
rest.get('*/api/v1/foo', (_req, res, ctx) => res(ctx.status(200), ctx.json({ ok: true })))
);
```
Keep large responses in `mocks-server/__mockdata_`.
## Interactions
- Prefer `userEvent` for real user interactions (click, type, select, tab).
- Use `fireEvent` only for low-level/programmatic events not covered by `userEvent` (e.g., scroll, resize, setting `element.scrollTop` for virtualization). Wrap in `act(...)` if needed.
- Always await interactions:
```ts
const user = userEvent.setup({ pointerEventsCheck: 0 });
await user.click(screen.getByRole('button', { name: /save/i }));
```
```ts
// Example: virtualized list scroll (no userEvent helper)
const scroller = container.querySelector('[data-test-id="virtuoso-scroller"]') as HTMLElement;
scroller.scrollTop = targetScrollTop;
act(() => { fireEvent.scroll(scroller); });
```
## Timers
❌ No global fake timers.
✅ Per-test only, for debounce/throttle:
```ts
jest.useFakeTimers();
const user = userEvent.setup({ advanceTimers: (ms) => jest.advanceTimersByTime(ms) });
await user.type(screen.getByRole('textbox'), 'query');
jest.advanceTimersByTime(400);
jest.useRealTimers();
```
## Queries
Prefer accessible queries (`getByRole`, `findByRole`, `getByLabelText`).
Fallback: visible text.
Last resort: `data-testid`.
# Example Test (using only configured global mocks)
```ts
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import { server, rest } from 'mocks-server/server';
import MyComponent from '../MyComponent';
describe('MyComponent', () => {
it('renders and interacts', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 })))
);
render(<MyComponent />, undefined, { initialRoute: '/foo' });
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /refresh/i }));
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
});
});
```
# Anti-patterns
❌ Importing RTL directly
❌ Using global fake timers
❌ Wrapping render in `act(...)`
❌ Mocking infra dependencies locally (router, react-query)
✅ Use our harness (`tests/test-utils`)
✅ Use MSW for API overrides
✅ Use userEvent + await
✅ Pin time only in tests that assert relative dates
# Best Practices
- **Critical Functionality**: Prioritize testing business logic and utilities
- **Dependency Mocking**: Global mocks for infra, local mocks for business logic
- **Data Scenarios**: Always test valid, invalid, and edge cases
- **Descriptive Names**: Make test intent clear
- **Organization**: Group related tests in describe
- **Consistency**: Match repo conventions
- **Edge Cases**: Test null, undefined, unexpected values
- **Limit Scope**: 35 focused tests per file
- **Use Helpers**: `rqSuccess`, `makeUser`, etc.
- **No Any**: Enforce type safety
# Example Test
```ts
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import { server, rest } from 'mocks-server/server';
import MyComponent from '../MyComponent';
describe('MyComponent', () => {
it('renders and interacts', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(
rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 })))
);
render(<MyComponent />, undefined, { initialRoute: '/foo' });
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /refresh/i }));
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
});
});
```
# Anti-patterns
❌ Importing RTL directly
❌ Using global fake timers
❌ Wrapping render in `act(...)`
❌ Mocking infra dependencies locally (router, react-query)
✅ Use our harness (`tests/test-utils`)
✅ Use MSW for API overrides
✅ Use userEvent + await
✅ Pin time only in tests that assert relative dates
# TypeScript Type Safety Examples
## Proper Mock Typing
```ts
// ✅ GOOD - Properly typed mocks
interface User {
id: number;
name: string;
email: string;
}
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// Type the mock functions
const mockFetchUser = jest.fn() as jest.MockedFunction<(id: number) => Promise<ApiResponse<User>>>;
const mockUpdateUser = jest.fn() as jest.MockedFunction<(user: User) => Promise<ApiResponse<User>>>;
// Mock implementation with proper typing
mockFetchUser.mockResolvedValue({
data: { id: 1, name: 'John Doe', email: 'john@example.com' },
status: 200,
message: 'Success'
});
// ❌ BAD - Using any type
const mockFetchUser = jest.fn() as any; // Don't do this
```
## React Component Testing with Types
```ts
// ✅ GOOD - Properly typed component testing
interface ComponentProps {
title: string;
data: User[];
onUserSelect: (user: User) => void;
isLoading?: boolean;
}
const TestComponent: React.FC<ComponentProps> = ({ title, data, onUserSelect, isLoading = false }) => {
// Component implementation
};
describe('TestComponent', () => {
it('should render with proper props', () => {
// Arrange - Type the props properly
const mockProps: ComponentProps = {
title: 'Test Title',
data: [{ id: 1, name: 'John', email: 'john@example.com' }],
onUserSelect: jest.fn() as jest.MockedFunction<(user: User) => void>,
isLoading: false
};
// Act
render(<TestComponent {...mockProps} />);
// Assert
expect(screen.getByText('Test Title')).toBeInTheDocument();
});
});
```
## Hook Testing with Types
```ts
// ✅ GOOD - Properly typed hook testing
interface UseUserDataReturn {
user: User | null;
loading: boolean;
error: string | null;
refetch: () => void;
}
const useUserData = (id: number): UseUserDataReturn => {
// Hook implementation
};
describe('useUserData', () => {
it('should return user data with proper typing', () => {
// Arrange
const mockUser: User = { id: 1, name: 'John', email: 'john@example.com' };
mockFetchUser.mockResolvedValue({
data: mockUser,
status: 200,
message: 'Success'
});
// Act
const { result } = renderHook(() => useUserData(1));
// Assert
expect(result.current.user).toEqual(mockUser);
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeNull();
});
});
```
## Global Mock Type Safety
```ts
// ✅ GOOD - Type-safe global mocks
// In __mocks__/routerMock.ts
export const mockUseLocation = (overrides: Partial<Location> = {}): Location => ({
pathname: '/traces',
search: '',
hash: '',
state: null,
key: 'test-key',
...overrides,
});
// In test files
const location = useLocation(); // Properly typed from global mock
expect(location.pathname).toBe('/traces');
```
# TypeScript Configuration for Jest
## Required Jest Configuration
```json
// jest.config.ts
{
"preset": "ts-jest/presets/js-with-ts-esm",
"globals": {
"ts-jest": {
"useESM": true,
"isolatedModules": true,
"tsconfig": "<rootDir>/tsconfig.jest.json"
}
},
"extensionsToTreatAsEsm": [".ts", ".tsx"],
"moduleFileExtensions": ["ts", "tsx", "js", "json"]
}
```
## TypeScript Jest Configuration
```json
// tsconfig.jest.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["jest", "@testing-library/jest-dom"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node"
},
"include": [
"src/**/*",
"**/*.test.ts",
"**/*.test.tsx",
"__mocks__/**/*"
]
}
```
## Common Type Safety Patterns
### Mock Function Typing
```ts
// ✅ GOOD - Proper mock function typing
const mockApiCall = jest.fn() as jest.MockedFunction<typeof apiCall>;
const mockEventHandler = jest.fn() as jest.MockedFunction<(event: Event) => void>;
// ❌ BAD - Using any
const mockApiCall = jest.fn() as any;
```
### Generic Mock Typing
```ts
// ✅ GOOD - Generic mock typing
interface MockApiResponse<T> {
data: T;
status: number;
}
const mockFetchData = jest.fn() as jest.MockedFunction<
<T>(endpoint: string) => Promise<MockApiResponse<T>>
>;
// Usage
mockFetchData<User>('/users').mockResolvedValue({
data: { id: 1, name: 'John' },
status: 200
});
```
### React Testing Library with Types
```ts
// ✅ GOOD - Typed testing utilities
import { render, screen, RenderResult } from '@testing-library/react';
import { ComponentProps } from 'react';
type TestComponentProps = ComponentProps<typeof TestComponent>;
const renderTestComponent = (props: Partial<TestComponentProps> = {}): RenderResult => {
const defaultProps: TestComponentProps = {
title: 'Test',
data: [],
onSelect: jest.fn(),
...props
};
return render(<TestComponent {...defaultProps} />);
};
```
### Error Handling with Types
```ts
// ✅ GOOD - Typed error handling
interface ApiError {
message: string;
code: number;
details?: Record<string, unknown>;
}
const mockApiError: ApiError = {
message: 'API Error',
code: 500,
details: { endpoint: '/users' }
};
mockFetchUser.mockRejectedValue(new Error(JSON.stringify(mockApiError)));
```
## Type Safety Checklist
- [ ] All mock functions use `jest.MockedFunction<T>`
- [ ] All mock data has proper interfaces
- [ ] No `any` types in test files
- [ ] Generic types are used where appropriate
- [ ] Error types are properly defined
- [ ] Component props are typed
- [ ] Hook return types are defined
- [ ] API response types are defined
- [ ] Global mocks are type-safe
- [ ] Test utilities are properly typed
# Mock Decision Tree
```
Is it used in 20+ test files?
├─ YES → Use Global Mock
│ ├─ react-router-dom
│ ├─ react-query
│ ├─ antd components
│ └─ browser APIs
└─ NO → Is it business logic?
├─ YES → Use Local Mock
│ ├─ API endpoints
│ ├─ Custom hooks
│ └─ Domain components
└─ NO → Is it test-specific?
├─ YES → Use Local Mock
│ ├─ Error scenarios
│ ├─ Loading states
│ └─ Specific data
└─ NO → Consider Global Mock
└─ If it becomes frequently used
```
# Common Anti-Patterns to Avoid
❌ **Don't mock global dependencies locally:**
```js
// BAD - This is already globally mocked
jest.mock('react-router-dom', () => ({ ... }));
```
❌ **Don't create global mocks for test-specific data:**
```js
// BAD - This should be local
jest.mock('../api/tracesService', () => ({
getTraces: jest.fn(() => specificTestData)
}));
```
✅ **Do use global mocks for infrastructure:**
```js
// GOOD - Use global mock
import { useLocation } from 'react-router-dom';
```
✅ **Do create local mocks for business logic:**
```js
// GOOD - Local mock for specific test needs
jest.mock('../api/tracesService', () => ({
getTraces: jest.fn(() => mockTracesData)
}));
```

View File

@@ -1,4 +1,5 @@
node_modules
build
*.typegen.ts
i18-generate-hash.js
i18-generate-hash.js
src/parser/TraceOperatorParser/**

View File

@@ -10,4 +10,6 @@ public/
**/*.json
# Ignore all files in parser folder:
src/parser/**
src/parser/**
src/TraceOperator/parser/**

View File

@@ -0,0 +1,51 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
// Mock for uplot library used in tests
export interface MockUPlotInstance {
setData: jest.Mock;
setSize: jest.Mock;
destroy: jest.Mock;
redraw: jest.Mock;
setSeries: jest.Mock;
}
export interface MockUPlotPaths {
spline: jest.Mock;
bars: jest.Mock;
}
// Create mock instance methods
const createMockUPlotInstance = (): MockUPlotInstance => ({
setData: jest.fn(),
setSize: jest.fn(),
destroy: jest.fn(),
redraw: jest.fn(),
setSeries: jest.fn(),
});
// Create mock paths
const mockPaths: MockUPlotPaths = {
spline: jest.fn(),
bars: jest.fn(),
};
// Mock static methods
const mockTzDate = jest.fn(
(date: Date, _timezone: string) => new Date(date.getTime()),
);
// Mock uPlot constructor - this needs to be a proper constructor function
function MockUPlot(
_options: unknown,
_data: unknown,
_target: HTMLElement,
): MockUPlotInstance {
return createMockUPlotInstance();
}
// Add static methods to the constructor
MockUPlot.tzDate = mockTzDate;
MockUPlot.paths = mockPaths;
// Export the constructor as default
export default MockUPlot;

View File

@@ -0,0 +1,29 @@
// Mock for useSafeNavigate hook to avoid React Router version conflicts in tests
interface SafeNavigateOptions {
replace?: boolean;
state?: unknown;
}
interface SafeNavigateTo {
pathname?: string;
search?: string;
hash?: string;
}
type SafeNavigateToType = string | SafeNavigateTo;
interface UseSafeNavigateReturn {
safeNavigate: jest.MockedFunction<
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
>;
}
export const useSafeNavigate = (): UseSafeNavigateReturn => ({
safeNavigate: jest.fn(
(to: SafeNavigateToType, options?: SafeNavigateOptions) => {
console.log(`Mock safeNavigate called with:`, to, options);
},
) as jest.MockedFunction<
(to: SafeNavigateToType, options?: SafeNavigateOptions) => void
>,
});

View File

@@ -1,9 +1,8 @@
import { Page } from '@playwright/test';
// Read credentials from environment variables
const username = process.env.LOGIN_USERNAME;
const password = process.env.LOGIN_PASSWORD;
const baseURL = process.env.BASE_URL;
const username = process.env.SIGNOZ_E2E_USERNAME;
const password = process.env.SIGNOZ_E2E_PASSWORD;
/**
* Ensures the user is logged in. If not, performs the login steps.
@@ -11,17 +10,17 @@ const baseURL = process.env.BASE_URL;
*/
export async function ensureLoggedIn(page: Page): Promise<void> {
// if already in home page, return
if (await page.url().includes('/home')) {
if (page.url().includes('/home')) {
return;
}
if (!username || !password) {
throw new Error(
'E2E_EMAIL and E2E_PASSWORD environment variables must be set.',
'SIGNOZ_E2E_USERNAME and SIGNOZ_E2E_PASSWORD environment variables must be set.',
);
}
await page.goto(`${baseURL}/login`);
await page.goto('/login');
await page.getByTestId('email').click();
await page.getByTestId('email').fill(username);
await page.getByTestId('initiate_login').click();

View File

@@ -4,4 +4,9 @@ FRONTEND_API_ENDPOINT="http://localhost:8080/"
PYLON_APP_ID="pylon-app-id"
APPCUES_APP_ID="appcess-app-id"
CI="1"
CI="1"
# Playwright E2E Test Configuration
SIGNOZ_E2E_BASE_URL="your-dev-environment-url"
SIGNOZ_E2E_USERNAME="your-email@example.com"
SIGNOZ_E2E_PASSWORD="your-password"

View File

@@ -1,6 +1,9 @@
import type { Config } from '@jest/types';
const USE_SAFE_NAVIGATE_MOCK_PATH = '<rootDir>/__mocks__/useSafeNavigate.ts';
const config: Config.InitialOptions = {
silent: true,
clearMocks: true,
coverageDirectory: 'coverage',
coverageReporters: ['text', 'cobertura', 'html', 'json-summary'],
@@ -10,6 +13,10 @@ const config: Config.InitialOptions = {
moduleNameMapper: {
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
'^uplot$': '<rootDir>/__mocks__/uplotMock.ts',
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
},
globals: {
extensionsToTreatAsEsm: ['.ts'],
@@ -26,7 +33,7 @@ const config: Config.InitialOptions = {
'^.+\\.(js|jsx)$': 'babel-jest',
},
transformIgnorePatterns: [
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn)/)',
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/sonner|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn)/)',
],
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
testPathIgnorePatterns: ['/node_modules/', '/public/'],

View File

@@ -18,7 +18,12 @@
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
"commitlint": "commitlint --edit $1",
"test": "jest",
"test:changedsince": "jest --changedSince=main --coverage --silent"
"test:changedsince": "jest --changedSince=main --coverage --silent",
"e2e": "playwright test",
"e2e:ui": "playwright test --ui",
"e2e:headed": "playwright test --headed",
"e2e:debug": "playwright test --debug",
"e2e:report": "playwright show-report"
},
"engines": {
"node": ">=16.15.0"
@@ -44,10 +49,16 @@
"@sentry/react": "8.41.0",
"@sentry/webpack-plugin": "2.22.6",
"@signozhq/badge": "0.0.2",
"@signozhq/button": "0.0.2",
"@signozhq/calendar": "0.0.0",
"@signozhq/callout": "0.0.2",
"@signozhq/design-tokens": "1.1.4",
"@signozhq/input": "0.0.2",
"@signozhq/popover": "0.0.0",
"@signozhq/resizable": "0.0.0",
"@signozhq/sonner": "0.1.0",
"@signozhq/table": "0.3.7",
"@signozhq/tooltip": "0.0.2",
"@tanstack/react-table": "8.20.6",
"@tanstack/react-virtual": "3.11.2",
"@uiw/codemirror-theme-copilot": "4.23.11",
@@ -133,6 +144,7 @@
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"rehype-raw": "7.0.0",
"rrule": "2.8.1",
"stream": "^0.0.2",
"style-loader": "1.3.0",
"styled-components": "^5.3.11",
@@ -271,6 +283,7 @@
"serialize-javascript": "6.0.2",
"prismjs": "1.30.0",
"got": "11.8.5",
"form-data": "4.0.4"
"form-data": "4.0.4",
"brace-expansion": "^2.0.2"
}
}

View File

@@ -45,14 +45,7 @@ export default defineConfig({
projects: [
{
name: 'chromium',
use: {
launchOptions: { args: ['--start-maximized'] },
viewport: null,
colorScheme: 'dark',
locale: 'en-US',
baseURL: 'https://app.us.staging.signoz.cloud',
trace: 'on-first-retry',
},
use: { ...devices['Desktop Chrome'] },
},
{

View File

@@ -0,0 +1,81 @@
<svg
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.11 16.8483L14.0369 17.7304C14.0369 17.7304 12.3481 22.0324 12.2437 22.3235C12.1392 22.6146 12.1437 23.0746 12.6037 23.0746C13.1881 23.0746 16.1546 23.0591 16.1546 23.0591C16.1546 23.0591 15.4346 26.5322 15.3924 26.8433C15.3502 27.1544 15.6835 27.4277 15.9768 27.1144C16.2701 26.8011 20.3121 21.6058 20.4877 21.3525C20.801 20.8992 20.4988 20.4814 20.1433 20.4614C19.7877 20.4414 17.4056 20.4925 17.4056 20.4925L19.11 16.8483Z"
fill="#FECA18"
/>
<path
d="M17.7589 17.4527C17.7589 17.4527 16.6279 19.9548 16.5856 20.097C16.4612 20.5192 17.0078 20.6903 17.1634 20.3481C17.3189 20.0037 18.6655 17.2194 18.6655 17.2194L17.7589 17.4527Z"
fill="#FDB900"
/>
<path
d="M12.8859 22.2592C13.1836 22.2503 15.7968 22.2436 16.0146 22.2281C16.4213 22.1969 16.4835 22.7591 16.0146 22.7591C15.528 22.7591 12.9637 22.768 12.8081 22.7747C12.4481 22.7925 12.3992 22.2747 12.8859 22.2592Z"
fill="#FDB900"
/>
<path
d="M14.9813 17.1127C14.9813 17.1127 13.6481 20.4592 13.5725 20.7103C13.2592 21.7591 14.3858 21.728 14.6836 21.1325C14.8302 20.837 16.2635 17.8016 16.3257 17.2527C16.3879 16.7061 14.9813 17.1127 14.9813 17.1127Z"
fill="#FFE36A"
/>
<path
d="M15.3347 21.0148C15.1436 20.8837 14.9125 20.9992 14.7725 21.2192C14.6325 21.4392 14.428 21.797 14.7414 21.9858C15.0236 22.1547 15.2724 21.8147 15.3835 21.657C15.4924 21.4992 15.6324 21.217 15.3347 21.0148Z"
fill="#FFE36A"
/>
<path
d="M17.6301 21.7326C17.1212 21.6237 16.9568 22.0459 16.8479 22.5459C16.739 23.0459 16.3302 24.6636 16.2546 25.0635C16.1457 25.6257 16.6612 25.7057 16.8812 25.188C17.0457 24.8013 17.759 23.057 17.8501 22.7792C17.9901 22.3592 18.1456 21.8437 17.6301 21.7326Z"
fill="#FFE36A"
/>
<path
d="M25.7585 12.0573C25.7585 12.0573 26.3363 4.28441 19.69 3.2978C13.7147 2.41118 12.5415 8.08421 12.5415 8.08421C12.5415 8.08421 10.2838 7.55757 8.66166 9.00639C7.05064 10.4463 7.17508 12.1507 7.17508 12.1507C7.17508 12.1507 3.20195 11.524 2.79531 14.935C2.41533 18.1215 7.11286 17.3282 7.11286 17.3282L29.4183 15.686C29.4183 15.686 29.9472 14.0106 28.2606 12.7462C27.2585 11.9929 25.7585 12.0573 25.7585 12.0573Z"
fill="#E4EAEE"
/>
<path
d="M13.7347 13.8196C13.9213 13.7574 14.9857 14.6662 18.0522 14.6951C22.2653 14.7373 25.4563 12.2552 25.4563 12.2552C25.4563 12.2552 25.5629 13.0108 25.2274 13.5485C24.8096 14.2151 24.163 14.3418 24.163 14.3418C24.163 14.3418 25.3318 15.1551 26.9362 15.0307C28.3828 14.9173 29.4294 14.4262 29.4294 14.4262C29.4294 14.4262 29.5694 15.1395 29.4916 15.8351C29.3361 17.2217 28.5228 17.7861 27.6251 17.8883C26.9651 17.9638 21.3276 17.9905 19.0122 18.0127C16.9256 18.0327 6.29285 18.2994 5.20624 18.1794C4.07963 18.0549 3.22412 17.3772 2.91303 16.4061C2.67526 15.6706 2.78859 15.1973 2.78859 15.1973C2.78859 15.1973 4.85959 15.7462 6.02175 15.6151C7.48167 15.4484 8.46162 14.5307 8.46162 14.5307C8.46162 14.5307 9.33713 15.0307 11.277 14.864C12.9458 14.7173 13.7347 13.8196 13.7347 13.8196Z"
fill="url(#paint0_radial_811_5475)"
/>
<path
d="M24.8653 18.2661C24.6386 18.1394 23.832 18.8927 23.3787 19.346C23.1009 19.6238 22.2165 20.3504 22.0965 21.0504C21.7988 22.7703 23.7698 23.1614 24.552 22.3637C25.143 21.7615 25.0364 20.586 25.0364 20.1904C25.0386 19.6416 25.1475 18.4216 24.8653 18.2661Z"
fill="#52C0EE"
/>
<path
d="M8.67058 19.1904C8.45504 19.0659 7.68174 19.7948 7.24621 20.237C6.97956 20.5058 6.13294 21.2103 6.01294 21.8947C5.71962 23.5723 7.5973 23.9657 8.34837 23.1924C8.91501 22.608 8.82168 21.4591 8.82391 21.0725C8.82613 20.537 8.93723 19.3459 8.67058 19.1904Z"
fill="#52C0EE"
/>
<path
d="M12.2548 24.1634C12.0126 24.0723 11.1971 24.8145 10.7438 25.2678C10.466 25.5456 9.64386 26.2566 9.52386 26.9566C9.2261 28.6765 11.1349 29.0832 11.9171 28.2854C12.5081 27.6832 12.4104 26.5077 12.4015 26.1122C12.3793 25.0812 12.4215 24.2256 12.2548 24.1634Z"
fill="#52C0EE"
/>
<path
d="M23.5054 20.4926C23.1943 20.3304 22.8165 20.3993 22.5765 20.8992C22.3365 21.3992 22.5343 21.797 22.7854 21.9103C23.0743 22.0436 23.432 21.9214 23.672 21.5147C23.912 21.1081 23.7654 20.6281 23.5054 20.4926Z"
fill="#B2E6FE"
/>
<path
d="M10.6682 26.4279C10.3482 26.3168 9.99715 26.4345 9.83716 26.9478C9.67717 27.4611 9.92382 27.8122 10.1794 27.8878C10.4749 27.9744 10.7993 27.8078 10.9727 27.3834C11.1438 26.9589 10.9349 26.5212 10.6682 26.4279Z"
fill="#B2E6FE"
/>
<path
d="M7.12613 21.3258C6.78837 21.2325 6.43283 21.3769 6.30395 21.9169C6.17507 22.4569 6.45061 22.8035 6.71948 22.8613C7.03058 22.9302 7.35278 22.7369 7.50389 22.288C7.65277 21.8436 7.41056 21.4036 7.12613 21.3258Z"
fill="#B2E6FE"
/>
<defs>
<radialGradient
id="paint0_radial_811_5475"
cx="0"
cy="0"
r="1"
gradientTransform="matrix(0.18837 -6.53799 9.79456 0.282554 16.401 18.5051)"
gradientUnits="userSpaceOnUse"
>
<stop offset="0.1934" stopColor="#FFE366" />
<stop offset="0.3305" stopColor="#EDDD82" />
<stop offset="0.5709" stopColor="#D0D4AD" />
<stop offset="0.7589" stopColor="#BFCFC7" />
<stop offset="0.8699" stopColor="#B8CDD1" />
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,28 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
AlertRuleV2,
PostableAlertRuleV2,
} from 'types/api/alerts/alertTypesV2';
export interface CreateAlertRuleResponse {
data: AlertRuleV2;
status: string;
}
const createAlertRule = async (
props: PostableAlertRuleV2,
): Promise<SuccessResponse<CreateAlertRuleResponse> | ErrorResponse> => {
const response = await axios.post(`/rules`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default createAlertRule;

View File

@@ -0,0 +1,28 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
export interface TestAlertRuleResponse {
data: {
alertCount: number;
message: string;
};
status: string;
}
const testAlertRule = async (
props: PostableAlertRuleV2,
): Promise<SuccessResponse<TestAlertRuleResponse> | ErrorResponse> => {
const response = await axios.post(`/testRule`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default testAlertRule;

View File

@@ -0,0 +1,26 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
export interface UpdateAlertRuleResponse {
data: string;
status: string;
}
const updateAlertRule = async (
id: string,
postableAlertRule: PostableAlertRuleV2,
): Promise<SuccessResponse<UpdateAlertRuleResponse> | ErrorResponse> => {
const response = await axios.put(`/rules/${id}`, {
...postableAlertRule,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default updateAlertRule;

View File

@@ -0,0 +1,115 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { ApiBaseInstance } from 'api';
import { getFieldKeys } from '../getFieldKeys';
// Mock the API instance
jest.mock('api', () => ({
ApiBaseInstance: {
get: jest.fn(),
},
}));
describe('getFieldKeys API', () => {
beforeEach(() => {
jest.clearAllMocks();
});
const mockSuccessResponse = {
status: 200,
data: {
status: 'success',
data: {
keys: {
'service.name': [],
'http.status_code': [],
},
complete: true,
},
},
};
it('should call API with correct parameters when no args provided', async () => {
// Mock successful API response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse);
// Call function with no parameters
await getFieldKeys();
// Verify API was called correctly with empty params object
expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/keys', {
params: {},
});
});
it('should call API with signal parameter when provided', async () => {
// Mock successful API response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse);
// Call function with signal parameter
await getFieldKeys('traces');
// Verify API was called with signal parameter
expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/keys', {
params: { signal: 'traces' },
});
});
it('should call API with name parameter when provided', async () => {
// Mock successful API response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({
status: 200,
data: {
status: 'success',
data: {
keys: { service: [] },
complete: false,
},
},
});
// Call function with name parameter
await getFieldKeys(undefined, 'service');
// Verify API was called with name parameter
expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/keys', {
params: { name: 'service' },
});
});
it('should call API with both signal and name when provided', async () => {
// Mock successful API response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({
status: 200,
data: {
status: 'success',
data: {
keys: { service: [] },
complete: false,
},
},
});
// Call function with both parameters
await getFieldKeys('logs', 'service');
// Verify API was called with both parameters
expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/keys', {
params: { signal: 'logs', name: 'service' },
});
});
it('should return properly formatted response', async () => {
// Mock API to return our response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockSuccessResponse);
// Call the function
const result = await getFieldKeys('traces');
// Verify the returned structure matches SuccessResponseV2 format
expect(result).toEqual({
httpStatusCode: 200,
data: mockSuccessResponse.data.data,
});
});
});

View File

@@ -0,0 +1,214 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { ApiBaseInstance } from 'api';
import { getFieldValues } from '../getFieldValues';
// Mock the API instance
jest.mock('api', () => ({
ApiBaseInstance: {
get: jest.fn(),
},
}));
describe('getFieldValues API', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should call the API with correct parameters (no options)', async () => {
// Mock API response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({
status: 200,
data: {
status: 'success',
data: {
values: {
stringValues: ['frontend', 'backend'],
},
complete: true,
},
},
});
// Call function without parameters
await getFieldValues();
// Verify API was called correctly with empty params
expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', {
params: {},
});
});
it('should call the API with signal parameter', async () => {
// Mock API response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({
status: 200,
data: {
status: 'success',
data: {
values: {
stringValues: ['frontend', 'backend'],
},
complete: true,
},
},
});
// Call function with signal parameter
await getFieldValues('traces');
// Verify API was called with signal parameter
expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', {
params: { signal: 'traces' },
});
});
it('should call the API with name parameter', async () => {
// Mock API response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({
status: 200,
data: {
status: 'success',
data: {
values: {
stringValues: ['frontend', 'backend'],
},
complete: true,
},
},
});
// Call function with name parameter
await getFieldValues(undefined, 'service.name');
// Verify API was called with name parameter
expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', {
params: { name: 'service.name' },
});
});
it('should call the API with value parameter', async () => {
// Mock API response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({
status: 200,
data: {
status: 'success',
data: {
values: {
stringValues: ['frontend'],
},
complete: false,
},
},
});
// Call function with value parameter
await getFieldValues(undefined, 'service.name', 'front');
// Verify API was called with value parameter
expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', {
params: { name: 'service.name', searchText: 'front' },
});
});
it('should call the API with time range parameters', async () => {
// Mock API response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce({
status: 200,
data: {
status: 'success',
data: {
values: {
stringValues: ['frontend', 'backend'],
},
complete: true,
},
},
});
// Call function with time range parameters
const startUnixMilli = 1625097600000000; // Note: nanoseconds
const endUnixMilli = 1625184000000000;
await getFieldValues(
'logs',
'service.name',
undefined,
startUnixMilli,
endUnixMilli,
);
// Verify API was called with time range parameters (converted to milliseconds)
expect(ApiBaseInstance.get).toHaveBeenCalledWith('/fields/values', {
params: {
signal: 'logs',
name: 'service.name',
startUnixMilli: '1625097600', // Should be converted to seconds (divided by 1000000)
endUnixMilli: '1625184000', // Should be converted to seconds (divided by 1000000)
},
});
});
it('should normalize the response values', async () => {
// Mock API response with multiple value types
const mockResponse = {
status: 200,
data: {
status: 'success',
data: {
values: {
stringValues: ['frontend', 'backend'],
numberValues: [200, 404],
boolValues: [true, false],
},
complete: true,
},
},
};
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockResponse);
// Call the function
const result = await getFieldValues('traces', 'mixed.values');
// Verify the response has normalized values array
expect(result.data?.normalizedValues).toContain('frontend');
expect(result.data?.normalizedValues).toContain('backend');
expect(result.data?.normalizedValues).toContain('200');
expect(result.data?.normalizedValues).toContain('404');
expect(result.data?.normalizedValues).toContain('true');
expect(result.data?.normalizedValues).toContain('false');
expect(result.data?.normalizedValues?.length).toBe(6);
});
it('should return a properly formatted success response', async () => {
// Create mock response
const mockApiResponse = {
status: 200,
data: {
status: 'success',
data: {
values: {
stringValues: ['frontend', 'backend'],
},
complete: true,
},
},
};
// Mock API to return our response
(ApiBaseInstance.get as jest.Mock).mockResolvedValueOnce(mockApiResponse);
// Call the function
const result = await getFieldValues('traces', 'service.name');
// Verify the returned structure matches SuccessResponseV2 format
expect(result).toEqual({
httpStatusCode: 200,
data: expect.objectContaining({
values: expect.any(Object),
normalizedValues: expect.any(Array),
complete: true,
}),
});
});
});

View File

@@ -0,0 +1,38 @@
import { ApiBaseInstance } from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { FieldKeyResponse } from 'types/api/dynamicVariables/getFieldKeys';
/**
* Get field keys for a given signal type
* @param signal Type of signal (traces, logs, metrics)
* @param name Optional search text
*/
export const getFieldKeys = async (
signal?: 'traces' | 'logs' | 'metrics',
name?: string,
): Promise<SuccessResponseV2<FieldKeyResponse>> => {
const params: Record<string, string> = {};
if (signal) {
params.signal = encodeURIComponent(signal);
}
if (name) {
params.name = encodeURIComponent(name);
}
try {
const response = await ApiBaseInstance.get('/fields/keys', { params });
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getFieldKeys;

View File

@@ -0,0 +1,87 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { ApiBaseInstance } from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { FieldValueResponse } from 'types/api/dynamicVariables/getFieldValues';
/**
* Get field values for a given signal type and field name
* @param signal Type of signal (traces, logs, metrics)
* @param name Name of the attribute for which values are being fetched
* @param value Optional search text
* @param existingQuery Optional existing query - across all present dynamic variables
*/
export const getFieldValues = async (
signal?: 'traces' | 'logs' | 'metrics',
name?: string,
searchText?: string,
startUnixMilli?: number,
endUnixMilli?: number,
existingQuery?: string,
): Promise<SuccessResponseV2<FieldValueResponse>> => {
const params: Record<string, string> = {};
if (signal) {
params.signal = encodeURIComponent(signal);
}
if (name) {
params.name = encodeURIComponent(name);
}
if (searchText) {
params.searchText = encodeURIComponent(searchText);
}
if (startUnixMilli) {
params.startUnixMilli = Math.floor(startUnixMilli / 1000000).toString();
}
if (endUnixMilli) {
params.endUnixMilli = Math.floor(endUnixMilli / 1000000).toString();
}
if (existingQuery) {
params.existingQuery = existingQuery;
}
try {
const response = await ApiBaseInstance.get('/fields/values', { params });
// Normalize values from different types (stringValues, boolValues, etc.)
if (response.data?.data?.values) {
const allValues: string[] = [];
Object.entries(response.data?.data?.values).forEach(
([key, valueArray]: [string, any]) => {
// Skip RelatedValues as they should be kept separate
if (key === 'relatedValues') {
return;
}
if (Array.isArray(valueArray)) {
allValues.push(...valueArray.map(String));
}
},
);
// Add a normalized values array to the response
response.data.data.normalizedValues = allValues;
// Add relatedValues to the response as per FieldValueResponse
if (response.data?.data?.values?.relatedValues) {
response.data.data.relatedValues =
response.data?.data?.values?.relatedValues;
}
}
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getFieldValues;

View File

@@ -0,0 +1,34 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponseV2, ErrorV2Resp, SuccessResponseV2 } from 'types/api';
export interface CreateRoutingPolicyBody {
name: string;
expression: string;
channels: string[];
description?: string;
}
export interface CreateRoutingPolicyResponse {
success: boolean;
message: string;
}
const createRoutingPolicy = async (
props: CreateRoutingPolicyBody,
): Promise<
SuccessResponseV2<CreateRoutingPolicyResponse> | ErrorResponseV2
> => {
try {
const response = await axios.post(`/route_policies`, props);
return {
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default createRoutingPolicy;

View File

@@ -0,0 +1,28 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponseV2, ErrorV2Resp, SuccessResponseV2 } from 'types/api';
export interface DeleteRoutingPolicyResponse {
success: boolean;
message: string;
}
const deleteRoutingPolicy = async (
routingPolicyId: string,
): Promise<
SuccessResponseV2<DeleteRoutingPolicyResponse> | ErrorResponseV2
> => {
try {
const response = await axios.delete(`/route_policies/${routingPolicyId}`);
return {
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default deleteRoutingPolicy;

View File

@@ -0,0 +1,40 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponseV2, ErrorV2Resp, SuccessResponseV2 } from 'types/api';
export interface ApiRoutingPolicy {
id: string;
name: string;
expression: string;
description: string;
channels: string[];
createdAt: string;
updatedAt: string;
createdBy: string;
updatedBy: string;
}
export interface GetRoutingPoliciesResponse {
status: string;
data?: ApiRoutingPolicy[];
}
export const getRoutingPolicies = async (
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponseV2<GetRoutingPoliciesResponse> | ErrorResponseV2> => {
try {
const response = await axios.get('/route_policies', {
signal,
headers,
});
return {
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View File

@@ -0,0 +1,38 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponseV2, ErrorV2Resp, SuccessResponseV2 } from 'types/api';
export interface UpdateRoutingPolicyBody {
name: string;
expression: string;
channels: string[];
description: string;
}
export interface UpdateRoutingPolicyResponse {
success: boolean;
message: string;
}
const updateRoutingPolicy = async (
id: string,
props: UpdateRoutingPolicyBody,
): Promise<
SuccessResponseV2<UpdateRoutingPolicyResponse> | ErrorResponseV2
> => {
try {
const response = await axios.put(`/route_policies/${id}`, {
...props,
});
return {
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default updateRoutingPolicy;

View File

@@ -0,0 +1,25 @@
import { ApiV2Instance } from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps } from 'types/api/settings/getRetention';
// Only works for logs
const getRetentionV2 = async (): Promise<
SuccessResponseV2<PayloadProps<'logs'>>
> => {
try {
const response = await ApiV2Instance.get<PayloadProps<'logs'>>(
`/settings/ttl`,
);
return {
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getRetentionV2;

View File

@@ -1,14 +1,14 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/settings/setRetention';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadPropsV2, Props } from 'types/api/settings/setRetention';
const setRetention = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
): Promise<SuccessResponseV2<PayloadPropsV2>> => {
try {
const response = await axios.post<PayloadProps>(
const response = await axios.post<PayloadPropsV2>(
`/settings/ttl?duration=${props.totalDuration}&type=${props.type}${
props.coldStorage
? `&coldStorage=${props.coldStorage}&toColdDuration=${props.toColdDuration}`
@@ -17,13 +17,11 @@ const setRetention = async (
);
return {
statusCode: 200,
error: null,
message: 'Success',
payload: response.data,
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View File

@@ -0,0 +1,32 @@
import { ApiV2Instance } from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadPropsV2, PropsV2 } from 'types/api/settings/setRetention';
const setRetentionV2 = async ({
type,
defaultTTLDays,
coldStorageVolume,
coldStorageDuration,
ttlConditions,
}: PropsV2): Promise<SuccessResponseV2<PayloadPropsV2>> => {
try {
const response = await ApiV2Instance.post<PayloadPropsV2>(`/settings/ttl`, {
type,
defaultTTLDays,
coldStorageVolume,
coldStorageDuration,
ttlConditions,
});
return {
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default setRetentionV2;

View File

@@ -0,0 +1,31 @@
import { ApiBaseInstance } from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/thirdPartyApis/listOverview';
const listOverview = async (
props: Props,
): Promise<SuccessResponseV2<PayloadProps>> => {
const { start, end, show_ip: showIp, filter } = props;
try {
const response = await ApiBaseInstance.post(
`/third-party-apis/overview/list`,
{
start,
end,
show_ip: showIp,
filter,
},
);
return {
httpStatusCode: response.status,
data: response.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default listOverview;

View File

@@ -0,0 +1,64 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp } from 'types/api';
import { ExportRawDataProps } from 'types/api/exportRawData/getExportRawData';
export const downloadExportData = async (
props: ExportRawDataProps,
): Promise<void> => {
try {
const queryParams = new URLSearchParams();
queryParams.append('start', String(props.start));
queryParams.append('end', String(props.end));
queryParams.append('filter', props.filter);
props.columns.forEach((col) => {
queryParams.append('columns', col);
});
queryParams.append('order_by', props.orderBy);
queryParams.append('limit', String(props.limit));
queryParams.append('format', props.format);
const response = await axios.get<Blob>(`export_raw_data?${queryParams}`, {
responseType: 'blob', // Important: tell axios to handle response as blob
decompress: true, // Enable automatic decompression
headers: {
Accept: 'application/octet-stream', // Tell server we expect binary data
},
timeout: 0,
});
// Only proceed if the response status is 200
if (response.status !== 200) {
throw new Error(
`Failed to download data: server returned status ${response.status}`,
);
}
// Create blob URL from response data
const blob = new Blob([response.data], { type: 'application/octet-stream' });
const url = window.URL.createObjectURL(blob);
// Create and configure download link
const link = document.createElement('a');
link.href = url;
// Get filename from Content-Disposition header or generate timestamped default
const filename =
response.headers['content-disposition']
?.split('filename=')[1]
?.replace(/["']/g, '') || `exported_data.${props.format || 'txt'}`;
link.setAttribute('download', filename);
// Trigger download
document.body.appendChild(link);
link.click();
link.remove();
URL.revokeObjectURL(url);
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default downloadExportData;

View File

@@ -2,7 +2,7 @@ import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/user/loginPrecheck';
import { Props, Signup as PayloadProps } from 'types/api/user/loginPrecheck';
const loginPrecheck = async (
props: Props,

View File

@@ -1,25 +1,21 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/user/loginPrecheck';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Signup } from 'types/api/user/loginPrecheck';
import { Props } from 'types/api/user/signup';
const signup = async (
props: Props,
): Promise<SuccessResponse<null | PayloadProps> | ErrorResponse> => {
const signup = async (props: Props): Promise<SuccessResponseV2<Signup>> => {
try {
const response = await axios.post(`/register`, {
const response = await axios.post<PayloadProps>(`/register`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data?.data,
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};

View File

@@ -124,7 +124,7 @@ export const FUNCTION_NAMES: Record<string, FunctionName> = {
RUNNING_DIFF: 'runningDiff',
LOG2: 'log2',
LOG10: 'log10',
CUM_SUM: 'cumSum',
CUM_SUM: 'cumulativeSum',
EWMA3: 'ewma3',
EWMA5: 'ewma5',
EWMA7: 'ewma7',

View File

@@ -92,6 +92,7 @@ describe('prepareQueryRangePayloadV5', () => {
builder: {
queryData: [baseBuilderQuery()],
queryFormulas: [baseFormula()],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.TIME_SERIES,
@@ -215,7 +216,7 @@ describe('prepareQueryRangePayloadV5', () => {
},
],
clickhouse_sql: [],
builder: { queryData: [], queryFormulas: [] },
builder: { queryData: [], queryFormulas: [], queryTraceOperator: [] },
},
graphType: PANEL_TYPES.TIME_SERIES,
originalGraphType: PANEL_TYPES.TABLE,
@@ -286,7 +287,7 @@ describe('prepareQueryRangePayloadV5', () => {
legend: 'LC',
},
],
builder: { queryData: [], queryFormulas: [] },
builder: { queryData: [], queryFormulas: [], queryTraceOperator: [] },
},
graphType: PANEL_TYPES.TABLE,
selectedTime: 'GLOBAL_TIME',
@@ -345,7 +346,7 @@ describe('prepareQueryRangePayloadV5', () => {
unit: undefined,
promql: [],
clickhouse_sql: [],
builder: { queryData: [], queryFormulas: [] },
builder: { queryData: [], queryFormulas: [], queryTraceOperator: [] },
},
graphType: PANEL_TYPES.TIME_SERIES,
selectedTime: 'GLOBAL_TIME',
@@ -386,6 +387,7 @@ describe('prepareQueryRangePayloadV5', () => {
builder: {
queryData: [baseBuilderQuery()],
queryFormulas: [],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.TABLE,
@@ -459,6 +461,7 @@ describe('prepareQueryRangePayloadV5', () => {
builder: {
queryData: [logsQuery],
queryFormulas: [],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.LIST,
@@ -572,6 +575,7 @@ describe('prepareQueryRangePayloadV5', () => {
},
],
queryFormulas: [],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.TIME_SERIES,
@@ -630,4 +634,260 @@ describe('prepareQueryRangePayloadV5', () => {
}),
);
});
it('builds payload for builder queries with filters array but no filter expression', () => {
const props: GetQueryResultsProps = {
query: {
queryType: EQueryType.QUERY_BUILDER,
id: 'q8',
unit: undefined,
promql: [],
clickhouse_sql: [],
builder: {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: { expression: '' },
filters: {
items: [
{
id: '1',
key: { key: 'service.name', type: 'string' },
op: '=',
value: 'payment-service',
},
{
id: '2',
key: { key: 'http.status_code', type: 'number' },
op: '>=',
value: 400,
},
{
id: '3',
key: { key: 'message', type: 'string' },
op: 'contains',
value: 'error',
},
],
op: 'AND',
},
}),
],
queryFormulas: [],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
start,
end,
};
const result = prepareQueryRangePayloadV5(props);
expect(result.legendMap).toEqual({ A: 'Legend A' });
expect(result.queryPayload.compositeQuery.queries).toHaveLength(1);
const builderQuery = result.queryPayload.compositeQuery.queries.find(
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.name).toBe('A');
expect(logSpec.signal).toBe('logs');
expect(logSpec.filter).toEqual({
expression:
"service.name = 'payment-service' AND http.status_code >= 400 AND message contains 'error'",
});
});
it('uses filter.expression when only expression is provided', () => {
const props: GetQueryResultsProps = {
query: {
queryType: EQueryType.QUERY_BUILDER,
id: 'q9',
unit: undefined,
promql: [],
clickhouse_sql: [],
builder: {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: { expression: 'http.status_code >= 500' },
filters: (undefined as unknown) as IBuilderQuery['filters'],
}),
],
queryFormulas: [],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
start,
end,
};
const result = prepareQueryRangePayloadV5(props);
const builderQuery = result.queryPayload.compositeQuery.queries.find(
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: 'http.status_code >= 500' });
});
it('derives expression from filters when filter is undefined', () => {
const props: GetQueryResultsProps = {
query: {
queryType: EQueryType.QUERY_BUILDER,
id: 'q10',
unit: undefined,
promql: [],
clickhouse_sql: [],
builder: {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: (undefined as unknown) as IBuilderQuery['filter'],
filters: {
items: [
{
id: '1',
key: { key: 'service.name', type: 'string' },
op: '=',
value: 'checkout',
},
],
op: 'AND',
},
}),
],
queryFormulas: [],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
start,
end,
};
const result = prepareQueryRangePayloadV5(props);
const builderQuery = result.queryPayload.compositeQuery.queries.find(
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: "service.name = 'checkout'" });
});
it('prefers filter.expression over filters when both are present', () => {
const props: GetQueryResultsProps = {
query: {
queryType: EQueryType.QUERY_BUILDER,
id: 'q11',
unit: undefined,
promql: [],
clickhouse_sql: [],
builder: {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: { expression: "service.name = 'frontend'" },
filters: {
items: [
{
id: '1',
key: { key: 'service.name', type: 'string' },
op: '=',
value: 'backend',
},
],
op: 'AND',
},
}),
],
queryFormulas: [],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
start,
end,
};
const result = prepareQueryRangePayloadV5(props);
const builderQuery = result.queryPayload.compositeQuery.queries.find(
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: "service.name = 'frontend'" });
});
it('returns empty expression when neither filter nor filters provided', () => {
const props: GetQueryResultsProps = {
query: {
queryType: EQueryType.QUERY_BUILDER,
id: 'q12',
unit: undefined,
promql: [],
clickhouse_sql: [],
builder: {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: (undefined as unknown) as IBuilderQuery['filter'],
filters: (undefined as unknown) as IBuilderQuery['filters'],
}),
],
queryFormulas: [],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
start,
end,
};
const result = prepareQueryRangePayloadV5(props);
const builderQuery = result.queryPayload.compositeQuery.queries.find(
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: '' });
});
it('returns empty expression when filters provided with empty items', () => {
const props: GetQueryResultsProps = {
query: {
queryType: EQueryType.QUERY_BUILDER,
id: 'q13',
unit: undefined,
promql: [],
clickhouse_sql: [],
builder: {
queryData: [
baseBuilderQuery({
dataSource: DataSource.LOGS,
filter: { expression: '' },
filters: { items: [], op: 'AND' },
}),
],
queryFormulas: [],
queryTraceOperator: [],
},
},
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
start,
end,
};
const result = prepareQueryRangePayloadV5(props);
const builderQuery = result.queryPayload.compositeQuery.queries.find(
(q) => q.type === 'builder_query',
) as QueryEnvelope;
const logSpec = builderQuery.spec as LogBuilderQuery;
expect(logSpec.filter).toEqual({ expression: '' });
});
});

View File

@@ -1,15 +1,21 @@
/* eslint-disable sonarjs/cognitive-complexity */
/* eslint-disable sonarjs/no-identical-functions */
import { convertFiltersToExpression } from 'components/QueryBuilderV2/utils';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import { isEmpty } from 'lodash-es';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import {
IBuilderQuery,
IBuilderTraceOperator,
} from 'types/api/queryBuilder/queryBuilderData';
import {
BaseBuilderQuery,
FieldContext,
FieldDataType,
Filter,
FunctionName,
GroupByKey,
Having,
@@ -24,6 +30,7 @@ import {
TelemetryFieldKey,
TraceAggregation,
VariableItem,
VariableType,
} from 'types/api/v5/queryRange';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
@@ -106,6 +113,23 @@ function isDeprecatedField(fieldName: string): boolean {
);
}
function getFilter(queryData: IBuilderQuery): Filter {
const { filter } = queryData;
if (filter?.expression) {
return {
expression: filter.expression,
};
}
if (queryData.filters && queryData.filters?.items?.length > 0) {
return convertFiltersToExpression(queryData.filters);
}
return {
expression: '',
};
}
function createBaseSpec(
queryData: IBuilderQuery,
requestType: RequestType,
@@ -119,7 +143,7 @@ function createBaseSpec(
return {
stepInterval: queryData?.stepInterval || null,
disabled: queryData.disabled,
filter: queryData?.filter?.expression ? queryData.filter : undefined,
filter: getFilter(queryData),
groupBy:
queryData.groupBy?.length > 0
? queryData.groupBy.map(
@@ -332,6 +356,109 @@ export function convertBuilderQueriesToV5(
);
}
function createTraceOperatorBaseSpec(
queryData: IBuilderTraceOperator,
requestType: RequestType,
panelType?: PANEL_TYPES,
): BaseBuilderQuery {
const nonEmptySelectColumns = (queryData.selectColumns as (
| BaseAutocompleteData
| TelemetryFieldKey
)[])?.filter((c) => ('key' in c ? c?.key : c?.name));
const {
stepInterval,
groupBy,
limit,
offset,
legend,
having,
orderBy,
pageSize,
} = queryData;
return {
stepInterval: stepInterval || undefined,
groupBy:
groupBy?.length > 0
? groupBy.map(
(item: any): GroupByKey => ({
name: item.key,
fieldDataType: item?.dataType,
fieldContext: item?.type,
description: item?.description,
unit: item?.unit,
signal: item?.signal,
materialized: item?.materialized,
}),
)
: undefined,
limit:
panelType === PANEL_TYPES.TABLE || panelType === PANEL_TYPES.LIST
? limit || pageSize || undefined
: limit || undefined,
offset: requestType === 'raw' || requestType === 'trace' ? offset : undefined,
order:
orderBy?.length > 0
? orderBy.map(
(order: any): OrderBy => ({
key: {
name: order.columnName,
},
direction: order.order,
}),
)
: undefined,
legend: isEmpty(legend) ? undefined : legend,
having: isEmpty(having) ? undefined : (having as Having),
selectFields: isEmpty(nonEmptySelectColumns)
? undefined
: nonEmptySelectColumns?.map(
(column: any): TelemetryFieldKey => ({
name: column.name ?? column.key,
fieldDataType:
column?.fieldDataType ?? (column?.dataType as FieldDataType),
fieldContext: column?.fieldContext ?? (column?.type as FieldContext),
signal: column?.signal ?? undefined,
}),
),
};
}
export function convertTraceOperatorToV5(
traceOperator: Record<string, IBuilderTraceOperator>,
requestType: RequestType,
panelType?: PANEL_TYPES,
): QueryEnvelope[] {
return Object.entries(traceOperator).map(
([queryName, traceOperatorData]): QueryEnvelope => {
const baseSpec = createTraceOperatorBaseSpec(
traceOperatorData,
requestType,
panelType,
);
// Skip aggregation for raw request type
const aggregations =
requestType === 'raw'
? undefined
: createAggregation(traceOperatorData, panelType);
const spec: QueryEnvelope['spec'] = {
name: queryName,
...baseSpec,
expression: traceOperatorData.expression || '',
aggregations: aggregations as TraceAggregation[],
};
return {
type: 'builder_trace_operator' as QueryType,
spec,
};
},
);
}
/**
* Converts PromQL queries to V5 format
*/
@@ -406,6 +533,7 @@ export const prepareQueryRangePayloadV5 = ({
formatForWeb,
originalGraphType,
fillGaps,
dynamicVariables,
}: GetQueryResultsProps): PrepareQueryRangePayloadV5Result => {
let legendMap: Record<string, string> = {};
const requestType = mapPanelTypeToRequestType(graphType);
@@ -413,14 +541,28 @@ export const prepareQueryRangePayloadV5 = ({
switch (query.queryType) {
case EQueryType.QUERY_BUILDER: {
const { queryData: data, queryFormulas } = query.builder;
const { queryData: data, queryFormulas, queryTraceOperator } = query.builder;
const currentQueryData = mapQueryDataToApi(data, 'queryName', tableParams);
const currentFormulas = mapQueryDataToApi(queryFormulas, 'queryName');
const filteredTraceOperator =
queryTraceOperator && queryTraceOperator.length > 0
? queryTraceOperator.filter((traceOperator) =>
Boolean(traceOperator.expression.trim()),
)
: [];
const currentTraceOperator = mapQueryDataToApi(
filteredTraceOperator,
'queryName',
tableParams,
);
// Combine legend maps
legendMap = {
...currentQueryData.newLegendMap,
...currentFormulas.newLegendMap,
...currentTraceOperator.newLegendMap,
};
// Convert builder queries
@@ -453,8 +595,14 @@ export const prepareQueryRangePayloadV5 = ({
}),
);
// Combine both types
queries = [...builderQueries, ...formulaQueries];
const traceOperatorQueries = convertTraceOperatorToV5(
currentTraceOperator.data,
requestType,
graphType,
);
// Combine all query types
queries = [...builderQueries, ...formulaQueries, ...traceOperatorQueries];
break;
}
case EQueryType.PROM: {
@@ -497,7 +645,12 @@ export const prepareQueryRangePayloadV5 = ({
fillGaps: fillGaps || false,
},
variables: Object.entries(variables).reduce((acc, [key, value]) => {
acc[key] = { value };
acc[key] = {
value,
type: dynamicVariables
?.find((v) => v.name === key)
?.type?.toLowerCase() as VariableType,
};
return acc;
}, {} as Record<string, VariableItem>),
};

View File

@@ -20,13 +20,15 @@
.ant-card-body {
height: calc(100% - 18px);
.widget-graph-container {
&.bar {
height: calc(100% - 110px);
}
.widget-graph-component-container {
.widget-graph-container {
&.bar-panel-container {
height: calc(100% - 110px);
}
&.graph {
height: calc(100% - 80px);
&.graph-panel-container {
height: calc(100% - 80px);
}
}
}
}
@@ -82,9 +84,11 @@
.ant-card-body {
height: calc(100% - 18px);
.widget-graph-container {
&.bar {
height: calc(100% - 110px);
.widget-graph-component-container {
.widget-graph-container {
&.bar-panel-container {
height: calc(100% - 110px);
}
}
}
}

View File

@@ -42,18 +42,31 @@ export function useNavigateToExplorer(): (
builder: {
...widgetQuery.builder,
queryData: widgetQuery.builder.queryData
.map((item) => ({
...item,
dataSource,
aggregateOperator: MetricAggregateOperator.NOOP,
filters: {
...item.filters,
items: [...(item.filters?.items || []), ...selectedFilters],
op: item.filters?.op || 'AND',
},
groupBy: [],
disabled: false,
}))
.map((item) => {
// filter out filters with unique ids
const seen = new Set();
const filterItems = [
...(item.filters?.items || []),
...selectedFilters,
].filter((item) => {
if (seen.has(item.id)) return false;
seen.add(item.id);
return true;
});
return {
...item,
dataSource,
aggregateOperator: MetricAggregateOperator.NOOP,
filters: {
...item.filters,
items: filterItems,
op: item.filters?.op || 'AND',
},
groupBy: [],
disabled: false,
};
})
.slice(0, 1),
queryFormulas: [],
},

View File

@@ -87,7 +87,7 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
const onClickUpdateWorkspace = (): void => {
window.open(
'https://github.com/SigNoz/signoz/releases',
'https://signoz.io/upgrade-path',
'_blank',
'noopener,noreferrer',
);

View File

@@ -91,7 +91,7 @@ describe('ChangelogModal', () => {
renderChangelog();
fireEvent.click(screen.getByText('Update my workspace'));
expect(window.open).toHaveBeenCalledWith(
'https://github.com/SigNoz/signoz/releases',
'https://signoz.io/upgrade-path',
'_blank',
'noopener,noreferrer',
);

View File

@@ -2,10 +2,28 @@
position: relative;
padding-left: 20px;
& :is(h1, h2, h3, h4, h5, h6, p, &-section-title) {
margin-bottom: 12px;
}
&-content {
display: flex;
flex-direction: column;
gap: 32px;
}
&-section-title {
font-size: 14px;
line-height: 20px;
color: var(--text-vanilla-400, #c0c1c3);
}
.changelog-release-date {
font-size: 14px;
line-height: 20px;
color: var(--text-vanilla-400, #c0c1c3);
display: block;
margin-bottom: 12px;
}
&-list {
@@ -81,12 +99,7 @@
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
& :is(h1, h2, h3, h4, h5, h6, p, &-section-title) {
font-weight: 600;
color: var(--text-vanilla-100, #fff);
}
@@ -96,7 +109,8 @@
line-height: 32px;
}
h2 {
h2,
&-section-title {
font-size: 20px;
line-height: 28px;
}
@@ -108,6 +122,7 @@
overflow: hidden;
border-radius: 4px;
border: 1px solid var(--bg-slate-400, #1d212d);
margin-bottom: 28px;
}
.changelog-media-video {
@@ -124,17 +139,8 @@
&-line {
background-color: var(--bg-vanilla-300);
}
li,
p {
color: var(--text-ink-500);
}
h1,
h2,
h3,
h4,
h5,
h6 {
& :is(h1, h2, h3, h4, h5, h6, p, li, &-section-title) {
color: var(--text-ink-500);
}

View File

@@ -55,33 +55,35 @@ function ChangelogRenderer({ changelog }: Props): JSX.Element {
<div className="inner-ball" />
</div>
<span className="changelog-release-date">{formattedReleaseDate}</span>
{changelog.features && changelog.features.length > 0 && (
<div className="changelog-renderer-list">
{changelog.features.map((feature) => (
<div key={feature.id}>
<h2>{feature.title}</h2>
{feature.media && renderMedia(feature.media)}
<ReactMarkdown>{feature.description}</ReactMarkdown>
</div>
))}
</div>
)}
{changelog.bug_fixes && changelog.bug_fixes.length > 0 && (
<div>
<h2>Bug Fixes</h2>
{changelog.bug_fixes && (
<ReactMarkdown>{changelog.bug_fixes}</ReactMarkdown>
)}
</div>
)}
{changelog.maintenance && changelog.maintenance.length > 0 && (
<div>
<h2>Maintenance</h2>
{changelog.maintenance && (
<ReactMarkdown>{changelog.maintenance}</ReactMarkdown>
)}
</div>
)}
<div className="changelog-renderer-content">
{changelog.features && changelog.features.length > 0 && (
<div className="changelog-renderer-list">
{changelog.features.map((feature) => (
<div key={feature.id}>
<div className="changelog-renderer-section-title">{feature.title}</div>
{feature.media && renderMedia(feature.media)}
<ReactMarkdown>{feature.description}</ReactMarkdown>
</div>
))}
</div>
)}
{changelog.bug_fixes && changelog.bug_fixes.length > 0 && (
<div className="changelog-renderer-bug-fixes">
<div className="changelog-renderer-section-title">Bug Fixes</div>
{changelog.bug_fixes && (
<ReactMarkdown>{changelog.bug_fixes}</ReactMarkdown>
)}
</div>
)}
{changelog.maintenance && changelog.maintenance.length > 0 && (
<div className="changelog-renderer-maintenance">
<div className="changelog-renderer-section-title">Maintenance</div>
{changelog.maintenance && (
<ReactMarkdown>{changelog.maintenance}</ReactMarkdown>
)}
</div>
)}
</div>
</div>
);
}

View File

@@ -174,6 +174,31 @@
cursor: pointer;
}
.time-input-prefix {
.live-dot-icon {
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var(--bg-forest-500);
animation: ripple 1s infinite;
margin-right: 4px;
margin-left: 4px;
}
}
@keyframes ripple {
0% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4);
}
70% {
box-shadow: 0 0 0 6px rgba(245, 158, 11, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
}
}
.time-input-suffix-icon-badge {
display: flex;
align-items: center;

View File

@@ -59,7 +59,9 @@ interface CustomTimePickerProps {
customDateTimeVisible?: boolean;
setCustomDTPickerVisible?: Dispatch<SetStateAction<boolean>>;
onCustomDateHandler?: (dateTimeRange: DateTimeRangeType) => void;
handleGoLive?: () => void;
showLiveLogs?: boolean;
onGoLive?: () => void;
onExitLiveLogs?: () => void;
}
function CustomTimePicker({
@@ -76,7 +78,9 @@ function CustomTimePicker({
customDateTimeVisible,
setCustomDTPickerVisible,
onCustomDateHandler,
handleGoLive,
onGoLive,
onExitLiveLogs,
showLiveLogs,
}: CustomTimePickerProps): JSX.Element {
const [
selectedTimePlaceholderValue,
@@ -165,9 +169,13 @@ function CustomTimePicker({
};
useEffect(() => {
const value = getSelectedTimeRangeLabel(selectedTime, selectedValue);
setSelectedTimePlaceholderValue(value);
}, [selectedTime, selectedValue]);
if (showLiveLogs) {
setSelectedTimePlaceholderValue('Live');
} else {
const value = getSelectedTimeRangeLabel(selectedTime, selectedValue);
setSelectedTimePlaceholderValue(value);
}
}, [selectedTime, selectedValue, showLiveLogs]);
const hide = (): void => {
setOpen(false);
@@ -338,6 +346,28 @@ function CustomTimePicker({
return '';
};
const getInputPrefix = (): JSX.Element => {
if (showLiveLogs) {
return (
<div className="time-input-prefix">
<div className="live-dot-icon" />
</div>
);
}
return (
<div className="time-input-prefix">
{inputValue && inputStatus === 'success' ? (
<CheckCircle size={14} color="#51E7A8" />
) : (
<Tooltip title="Enter time in format (e.g., 1m, 2h, 3d, 4w)">
<Clock size={14} className="cursor-pointer" />
</Tooltip>
)}
</div>
);
};
return (
<div className="custom-time-picker">
<Tooltip title={getTooltipTitle()} placement="top">
@@ -357,7 +387,8 @@ function CustomTimePicker({
setCustomDTPickerVisible={defaultTo(setCustomDTPickerVisible, noop)}
onCustomDateHandler={defaultTo(onCustomDateHandler, noop)}
onSelectHandler={handleSelect}
handleGoLive={defaultTo(handleGoLive, noop)}
onGoLive={defaultTo(onGoLive, noop)}
onExitLiveLogs={defaultTo(onExitLiveLogs, noop)}
options={items}
selectedTime={selectedTime}
activeView={activeView}
@@ -392,17 +423,7 @@ function CustomTimePicker({
onBlur={handleBlur}
onChange={handleInputChange}
data-1p-ignore
prefix={
<div className="time-input-prefix">
{inputValue && inputStatus === 'success' ? (
<CheckCircle size={14} color="#51E7A8" />
) : (
<Tooltip title="Enter time in format (e.g., 1m, 2h, 3d, 4w)">
<Clock size={14} className="cursor-pointer" />
</Tooltip>
)}
</div>
}
prefix={getInputPrefix()}
suffix={
<div className="time-input-suffix">
{!!isTimezoneOverridden && activeTimezoneOffset && (
@@ -439,6 +460,8 @@ CustomTimePicker.defaultProps = {
customDateTimeVisible: false,
setCustomDTPickerVisible: noop,
onCustomDateHandler: noop,
handleGoLive: noop,
onGoLive: noop,
onCustomTimeStatusUpdate: noop,
onExitLiveLogs: noop,
showLiveLogs: false,
};

View File

@@ -6,6 +6,7 @@ import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import DatePickerV2 from 'components/DatePickerV2/DatePickerV2';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
import {
@@ -16,7 +17,14 @@ import {
import dayjs from 'dayjs';
import { Clock, PenLine } from 'lucide-react';
import { useTimezone } from 'providers/Timezone';
import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react';
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useLocation } from 'react-router-dom';
import { getCustomTimeRanges } from 'utils/customTimeRangeUtils';
@@ -32,12 +40,13 @@ interface CustomTimePickerPopoverContentProps {
lexicalContext?: LexicalContext,
) => void;
onSelectHandler: (label: string, value: string) => void;
handleGoLive: () => void;
onGoLive: () => void;
selectedTime: string;
activeView: 'datetime' | 'timezone';
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
isOpenedFromFooter: boolean;
setIsOpenedFromFooter: Dispatch<SetStateAction<boolean>>;
onExitLiveLogs: () => void;
}
interface RecentlyUsedDateTimeRange {
@@ -56,12 +65,13 @@ function CustomTimePickerPopoverContent({
setCustomDTPickerVisible,
onCustomDateHandler,
onSelectHandler,
handleGoLive,
onGoLive,
selectedTime,
activeView,
setActiveView,
isOpenedFromFooter,
setIsOpenedFromFooter,
onExitLiveLogs,
}: CustomTimePickerPopoverContentProps): JSX.Element {
const { pathname } = useLocation();
@@ -69,6 +79,19 @@ function CustomTimePickerPopoverContent({
pathname,
]);
const url = new URLSearchParams(window.location.search);
let panelTypeFromURL = url.get(QueryParams.panelTypes);
try {
panelTypeFromURL = JSON.parse(panelTypeFromURL as string);
} catch {
// fallback → leave as-is
}
const isLogsListView =
panelTypeFromURL !== 'table' && panelTypeFromURL !== 'graph'; // we do not select list view in the url
const { timezone } = useTimezone();
const activeTimezoneOffset = timezone.offset;
@@ -76,6 +99,12 @@ function CustomTimePickerPopoverContent({
RecentlyUsedDateTimeRange[]
>([]);
const handleExitLiveLogs = useCallback((): void => {
if (isLogsExplorerPage) {
onExitLiveLogs();
}
}, [isLogsExplorerPage, onExitLiveLogs]);
useEffect(() => {
if (!customDateTimeVisible) {
const customTimeRanges = getCustomTimeRanges();
@@ -107,6 +136,7 @@ function CustomTimePickerPopoverContent({
className="time-btns"
key={option.label + option.value}
onClick={(): void => {
handleExitLiveLogs();
onSelectHandler(option.label, option.value);
}}
>
@@ -140,12 +170,17 @@ function CustomTimePickerPopoverContent({
);
}
const handleGoLive = (): void => {
onGoLive();
setIsOpen(false);
};
return (
<>
<div className="date-time-popover">
{!customDateTimeVisible && (
<div className="date-time-options">
{isLogsExplorerPage && (
{isLogsExplorerPage && isLogsListView && (
<Button className="data-time-live" type="text" onClick={handleGoLive}>
Live
</Button>
@@ -155,6 +190,7 @@ function CustomTimePickerPopoverContent({
type="text"
key={option.label + option.value}
onClick={(): void => {
handleExitLiveLogs();
onSelectHandler(option.label, option.value);
}}
className={cx(
@@ -169,7 +205,6 @@ function CustomTimePickerPopoverContent({
))}
</div>
)}
<div
className={cx(
'relative-date-time',
@@ -199,12 +234,14 @@ function CustomTimePickerPopoverContent({
tabIndex={0}
onKeyDown={(e): void => {
if (e.key === 'Enter' || e.key === ' ') {
handleExitLiveLogs();
onCustomDateHandler([dayjs(range.from), dayjs(range.to)]);
setIsOpen(false);
}
}}
key={range.value}
onClick={(): void => {
handleExitLiveLogs();
onCustomDateHandler([dayjs(range.from), dayjs(range.to)]);
setIsOpen(false);
}}

View File

@@ -119,7 +119,9 @@ const filterAndSortTimezones = (
return createTimezoneEntry(normalizedTz, offset);
});
const generateTimezoneData = (includeEtcTimezones = false): Timezone[] => {
export const generateTimezoneData = (
includeEtcTimezones = false,
): Timezone[] => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const allTimezones = (Intl as any).supportedValuesOf('timeZone');
const timezones: Timezone[] = [];

View File

@@ -19,20 +19,6 @@ beforeAll(() => {
});
});
jest.mock('uplot', () => {
const paths = {
spline: jest.fn(),
bars: jest.fn(),
};
const uplotMock = jest.fn(() => ({
paths,
}));
return {
paths,
default: uplotMock,
};
});
jest.mock('react-dnd', () => ({
useDrop: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),
useDrag: jest.fn().mockImplementation(() => [jest.fn(), jest.fn(), jest.fn()]),

View File

@@ -0,0 +1,117 @@
# withErrorBoundary HOC
A Higher-Order Component (HOC) that wraps React components with ErrorBoundary to provide error handling and recovery.
## Features
- **Automatic Error Catching**: Catches JavaScript errors in any component tree
- **Integration**: Automatically reports errors with context
- **Custom Fallback UI**: Supports custom error fallback components
- **Error Logging**: Optional custom error handlers for additional logging
- **TypeScript Support**: Fully typed with proper generics
- **Component Context**: Automatically adds component name to tags
## Basic Usage
```tsx
import { withErrorBoundary } from 'components/HOC';
// Wrap any component
const SafeComponent = withErrorBoundary(MyComponent);
// Use it like any other component
<SafeComponent prop1="value1" prop2="value2" />
```
## Advanced Usage
### Custom Fallback Component
```tsx
const CustomFallback = () => (
<div>
<h3>Oops! Something went wrong</h3>
<button onClick={() => window.location.reload()}>Reload</button>
</div>
);
const SafeComponent = withErrorBoundary(MyComponent, {
fallback: <CustomFallback />
});
```
### Custom Error Handler
```tsx
const SafeComponent = withErrorBoundary(MyComponent, {
onError: (error, componentStack, eventId) => {
console.error('Component error:', error);
// Send to analytics, logging service, etc.
}
});
```
### Sentry Configuration
```tsx
const SafeComponent = withErrorBoundary(MyComponent, {
sentryOptions: {
tags: {
section: 'dashboard',
priority: 'high',
feature: 'metrics'
},
level: 'error'
}
});
```
## API Reference
### `withErrorBoundary<P>(component, options?)`
#### Parameters
- `component: ComponentType<P>` - The React component to wrap
- `options?: WithErrorBoundaryOptions` - Configuration options
#### Options
```tsx
interface WithErrorBoundaryOptions {
/** Custom fallback component to render when an error occurs */
fallback?: ReactElement;
/** Custom error handler function */
onError?: (
error: unknown,
componentStack: string | undefined,
eventId: string
) => void;
/** Additional props to pass to the Sentry ErrorBoundary */
sentryOptions?: {
tags?: Record<string, string>;
level?: Sentry.SeverityLevel;
};
}
```
## When to Use
- **Critical Components**: Wrap important UI components that shouldn't crash the entire app
- **Third-party Integrations**: Wrap components that use external libraries
- **Data-heavy Components**: Wrap components that process complex data
- **Route Components**: Wrap page-level components to prevent navigation issues
## Best Practices
1. **Use Sparingly**: Don't wrap every component - focus on critical ones
2. **Meaningful Fallbacks**: Provide helpful fallback UI that guides users
3. **Log Errors**: Always implement error logging for debugging
4. **Component Names**: Ensure components have proper `displayName` for debugging
5. **Test Error Scenarios**: Test that your error boundaries work as expected
## Examples
See `withErrorBoundary.example.tsx` for complete usage examples.

View File

@@ -0,0 +1,211 @@
import { render, screen } from '@testing-library/react';
import React from 'react';
import withErrorBoundary, {
WithErrorBoundaryOptions,
} from '../withErrorBoundary';
// Mock dependencies before imports
jest.mock('@sentry/react', () => {
const ReactMock = jest.requireActual('react');
class MockErrorBoundary extends ReactMock.Component<
{
children: React.ReactNode;
fallback: React.ReactElement;
onError?: (error: Error, componentStack: string, eventId: string) => void;
beforeCapture?: (scope: {
setTag: (key: string, value: string) => void;
setLevel: (level: string) => void;
}) => void;
},
{ hasError: boolean }
> {
constructor(props: MockErrorBoundary['props']) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(): { hasError: boolean } {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: { componentStack: string }): void {
const { beforeCapture, onError } = this.props;
if (beforeCapture) {
const mockScope = {
setTag: jest.fn(),
setLevel: jest.fn(),
};
beforeCapture(mockScope);
}
if (onError) {
onError(error, errorInfo.componentStack, 'mock-event-id');
}
}
render(): React.ReactNode {
const { hasError } = this.state;
const { fallback, children } = this.props;
if (hasError) {
return <div data-testid="error-boundary-fallback">{fallback}</div>;
}
return <div data-testid="app-error-boundary">{children}</div>;
}
}
return {
ErrorBoundary: MockErrorBoundary,
SeverityLevel: {
error: 'error',
warning: 'warning',
info: 'info',
},
};
});
jest.mock(
'../../../pages/ErrorBoundaryFallback/ErrorBoundaryFallback',
() =>
function MockErrorBoundaryFallback(): JSX.Element {
return (
<div data-testid="default-error-fallback">Default Error Fallback</div>
);
},
);
// Test component that can throw errors
interface TestComponentProps {
shouldThrow?: boolean;
message?: string;
}
function TestComponent({
shouldThrow = false,
message = 'Test Component',
}: TestComponentProps): JSX.Element {
if (shouldThrow) {
throw new Error('Test error');
}
return <div data-testid="test-component">{message}</div>;
}
TestComponent.defaultProps = {
shouldThrow: false,
message: 'Test Component',
};
// Test component with display name
function NamedComponent(): JSX.Element {
return <div data-testid="named-component">Named Component</div>;
}
NamedComponent.displayName = 'NamedComponent';
describe('withErrorBoundary', () => {
// Suppress console errors for cleaner test output
const originalError = console.error;
beforeAll(() => {
console.error = jest.fn();
});
afterAll(() => {
console.error = originalError;
});
beforeEach(() => {
jest.clearAllMocks();
});
it('should wrap component with ErrorBoundary and render successfully', () => {
// Arrange
const SafeComponent = withErrorBoundary(TestComponent);
// Act
render(<SafeComponent message="Hello World" />);
// Assert
expect(screen.getByTestId('app-error-boundary')).toBeInTheDocument();
expect(screen.getByTestId('test-component')).toBeInTheDocument();
expect(screen.getByText('Hello World')).toBeInTheDocument();
});
it('should render fallback UI when component throws error', () => {
// Arrange
const SafeComponent = withErrorBoundary(TestComponent);
// Act
render(<SafeComponent shouldThrow />);
// Assert
expect(screen.getByTestId('error-boundary-fallback')).toBeInTheDocument();
expect(screen.getByTestId('default-error-fallback')).toBeInTheDocument();
});
it('should render custom fallback component when provided', () => {
// Arrange
const customFallback = (
<div data-testid="custom-fallback">Custom Error UI</div>
);
const options: WithErrorBoundaryOptions = {
fallback: customFallback,
};
const SafeComponent = withErrorBoundary(TestComponent, options);
// Act
render(<SafeComponent shouldThrow />);
// Assert
expect(screen.getByTestId('error-boundary-fallback')).toBeInTheDocument();
expect(screen.getByTestId('custom-fallback')).toBeInTheDocument();
expect(screen.getByText('Custom Error UI')).toBeInTheDocument();
});
it('should call custom error handler when error occurs', () => {
// Arrange
const mockErrorHandler = jest.fn();
const options: WithErrorBoundaryOptions = {
onError: mockErrorHandler,
};
const SafeComponent = withErrorBoundary(TestComponent, options);
// Act
render(<SafeComponent shouldThrow />);
// Assert
expect(mockErrorHandler).toHaveBeenCalledWith(
expect.any(Error),
expect.any(String),
'mock-event-id',
);
expect(mockErrorHandler).toHaveBeenCalledTimes(1);
});
it('should set correct display name for debugging', () => {
// Arrange & Act
const SafeTestComponent = withErrorBoundary(TestComponent);
const SafeNamedComponent = withErrorBoundary(NamedComponent);
// Assert
expect(SafeTestComponent.displayName).toBe(
'withErrorBoundary(TestComponent)',
);
expect(SafeNamedComponent.displayName).toBe(
'withErrorBoundary(NamedComponent)',
);
});
it('should handle component without display name', () => {
// Arrange
function AnonymousComponent(): JSX.Element {
return <div>Anonymous</div>;
}
// Act
const SafeAnonymousComponent = withErrorBoundary(AnonymousComponent);
// Assert
expect(SafeAnonymousComponent.displayName).toBe(
'withErrorBoundary(AnonymousComponent)',
);
});
});

View File

@@ -0,0 +1,2 @@
export type { WithErrorBoundaryOptions } from './withErrorBoundary';
export { default as withErrorBoundary } from './withErrorBoundary';

View File

@@ -0,0 +1,143 @@
import { Button } from 'antd';
import { useState } from 'react';
import { withErrorBoundary } from './index';
/**
* Example component that can throw errors
*/
function ProblematicComponent(): JSX.Element {
const [shouldThrow, setShouldThrow] = useState(false);
if (shouldThrow) {
throw new Error('This is a test error from ProblematicComponent!');
}
return (
<div style={{ padding: '20px' }}>
<h3>Problematic Component</h3>
<p>This component can throw errors when the button is clicked.</p>
<Button type="primary" onClick={(): void => setShouldThrow(true)} danger>
Trigger Error
</Button>
</div>
);
}
/**
* Basic usage - wraps component with default error boundary
*/
export const SafeProblematicComponent = withErrorBoundary(ProblematicComponent);
/**
* Usage with custom fallback component
*/
function CustomErrorFallback(): JSX.Element {
return (
<div
style={{ padding: '20px', border: '1px solid red', borderRadius: '4px' }}
>
<h4 style={{ color: 'red' }}>Custom Error Fallback</h4>
<p>Something went wrong in this specific component!</p>
<Button onClick={(): void => window.location.reload()}>Reload Page</Button>
</div>
);
}
export const SafeProblematicComponentWithCustomFallback = withErrorBoundary(
ProblematicComponent,
{
fallback: <CustomErrorFallback />,
},
);
/**
* Usage with custom error handler
*/
export const SafeProblematicComponentWithErrorHandler = withErrorBoundary(
ProblematicComponent,
{
onError: (error, errorInfo) => {
console.error('Custom error handler:', error);
console.error('Error info:', errorInfo);
// You could also send to analytics, logging service, etc.
},
sentryOptions: {
tags: {
section: 'dashboard',
priority: 'high',
},
level: 'error',
},
},
);
/**
* Example of wrapping an existing component from the codebase
*/
function ExistingComponent({
title,
data,
}: {
title: string;
data: any[];
}): JSX.Element {
// This could be any existing component that might throw errors
return (
<div>
<h4>{title}</h4>
<ul>
{data.map((item, index) => (
// eslint-disable-next-line react/no-array-index-key
<li key={index}>{item.name}</li>
))}
</ul>
</div>
);
}
export const SafeExistingComponent = withErrorBoundary(ExistingComponent, {
sentryOptions: {
tags: {
component: 'ExistingComponent',
feature: 'data-display',
},
},
});
/**
* Usage examples in a container component
*/
export function ErrorBoundaryExamples(): JSX.Element {
const sampleData = [
{ name: 'Item 1' },
{ name: 'Item 2' },
{ name: 'Item 3' },
];
return (
<div style={{ padding: '20px' }}>
<h2>Error Boundary HOC Examples</h2>
<div style={{ marginBottom: '20px' }}>
<h3>1. Basic Usage</h3>
<SafeProblematicComponent />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>2. With Custom Fallback</h3>
<SafeProblematicComponentWithCustomFallback />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>3. With Custom Error Handler</h3>
<SafeProblematicComponentWithErrorHandler />
</div>
<div style={{ marginBottom: '20px' }}>
<h3>4. Wrapped Existing Component</h3>
<SafeExistingComponent title="Sample Data" data={sampleData} />
</div>
</div>
);
}

View File

@@ -0,0 +1,99 @@
import * as Sentry from '@sentry/react';
import { ComponentType, ReactElement } from 'react';
import ErrorBoundaryFallback from '../../pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
/**
* Configuration options for the ErrorBoundary HOC
*/
interface WithErrorBoundaryOptions {
/** Custom fallback component to render when an error occurs */
fallback?: ReactElement;
/** Custom error handler function */
onError?: (
error: unknown,
componentStack: string | undefined,
eventId: string,
) => void;
/** Additional props to pass to the ErrorBoundary */
sentryOptions?: {
tags?: Record<string, string>;
level?: Sentry.SeverityLevel;
};
}
/**
* Higher-Order Component that wraps a component with ErrorBoundary
*
* @param WrappedComponent - The component to wrap with error boundary
* @param options - Configuration options for the error boundary
*
* @example
* // Basic usage
* const SafeComponent = withErrorBoundary(MyComponent);
*
* @example
* // With custom fallback
* const SafeComponent = withErrorBoundary(MyComponent, {
* fallback: <div>Something went wrong!</div>
* });
*
* @example
* // With custom error handler
* const SafeComponent = withErrorBoundary(MyComponent, {
* onError: (error, errorInfo) => {
* console.error('Component error:', error, errorInfo);
* }
* });
*/
function withErrorBoundary<P extends Record<string, unknown>>(
WrappedComponent: ComponentType<P>,
options: WithErrorBoundaryOptions = {},
): ComponentType<P> {
const {
fallback = <ErrorBoundaryFallback />,
onError,
sentryOptions = {},
} = options;
function WithErrorBoundaryComponent(props: P): JSX.Element {
return (
<Sentry.ErrorBoundary
fallback={fallback}
beforeCapture={(scope): void => {
// Add component name to context
scope.setTag(
'component',
WrappedComponent.displayName || WrappedComponent.name || 'Unknown',
);
// Add any custom tags
if (sentryOptions.tags) {
Object.entries(sentryOptions.tags).forEach(([key, value]) => {
scope.setTag(key, value);
});
}
// Set severity level if provided
if (sentryOptions.level) {
scope.setLevel(sentryOptions.level);
}
}}
onError={onError}
>
{/* eslint-disable-next-line react/jsx-props-no-spreading */}
<WrappedComponent {...props} />
</Sentry.ErrorBoundary>
);
}
// Set display name for debugging purposes
WithErrorBoundaryComponent.displayName = `withErrorBoundary(${
WrappedComponent.displayName || WrappedComponent.name || 'Component'
})`;
return WithErrorBoundaryComponent;
}
export default withErrorBoundary;
export type { WithErrorBoundaryOptions };

View File

@@ -1,4 +1,4 @@
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import APIError from 'types/api/error';
import ErrorModal from './ErrorModal';
@@ -56,9 +56,8 @@ describe('ErrorModal Component', () => {
// Click the close button
const closeButton = screen.getByTestId('close-button');
act(() => {
fireEvent.click(closeButton);
});
const user = userEvent.setup({ pointerEventsCheck: 0 });
await user.click(closeButton);
// Check if onClose was called
expect(onCloseMock).toHaveBeenCalledTimes(1);
@@ -149,9 +148,8 @@ it('should open the modal when the trigger component is clicked', async () => {
// Click the trigger component
const triggerButton = screen.getByText('Open Error Modal');
act(() => {
fireEvent.click(triggerButton);
});
const user = userEvent.setup({ pointerEventsCheck: 0 });
await user.click(triggerButton);
// Check if the modal is displayed
expect(screen.getByText('An error occurred')).toBeInTheDocument();
@@ -170,18 +168,15 @@ it('should close the modal when the onCancel event is triggered', async () => {
// Click the trigger component
const triggerButton = screen.getByText('error');
act(() => {
fireEvent.click(triggerButton);
});
const user = userEvent.setup({ pointerEventsCheck: 0 });
await user.click(triggerButton);
await waitFor(() => {
expect(screen.getByText('An error occurred')).toBeInTheDocument();
});
// Trigger the onCancel event
act(() => {
fireEvent.click(screen.getByTestId('close-button'));
});
await user.click(screen.getByTestId('close-button'));
// Check if the modal is closed
expect(onCloseMock).toHaveBeenCalledTimes(1);

View File

@@ -0,0 +1,15 @@
import { Typography } from 'antd';
function AnnouncementsModal(): JSX.Element {
return (
<div className="announcements-modal-container">
<div className="announcements-modal-container-header">
<Typography.Text className="announcements-modal-title">
Announcements
</Typography.Text>
</div>
</div>
);
}
export default AnnouncementsModal;

View File

@@ -0,0 +1,160 @@
import { toast } from '@signozhq/sonner';
import { Button, Input, Radio, RadioChangeEvent, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { handleContactSupport } from 'pages/Integrations/utils';
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
const [activeTab, setActiveTab] = useState('feedback');
const [feedback, setFeedback] = useState('');
const location = useLocation();
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const [isLoading, setIsLoading] = useState(false);
const handleSubmit = async (): Promise<void> => {
setIsLoading(true);
let entityName = 'Feedback';
if (activeTab === 'reportBug') {
entityName = 'Bug report';
} else if (activeTab === 'featureRequest') {
entityName = 'Feature request';
}
logEvent('Feedback: Submitted', {
data: feedback,
type: activeTab,
page: location.pathname,
})
.then(() => {
onClose();
toast.success(`${entityName} submitted successfully`, {
position: 'top-right',
});
})
.catch(() => {
console.error(`Failed to submit ${entityName}`);
toast.error(`Failed to submit ${entityName}`, {
position: 'top-right',
});
})
.finally(() => {
setIsLoading(false);
});
};
useEffect(
() => (): void => {
setFeedback('');
setActiveTab('feedback');
},
[],
);
const items = [
{
label: (
<div className="feedback-modal-tab-label">
<div className="tab-icon dot feedback-tab" />
Feedback
</div>
),
key: 'feedback',
value: 'feedback',
},
{
label: (
<div className="feedback-modal-tab-label">
<div className="tab-icon dot bug-tab" />
Report a bug
</div>
),
key: 'reportBug',
value: 'reportBug',
},
{
label: (
<div className="feedback-modal-tab-label">
<div className="tab-icon dot feature-tab" />
Feature request
</div>
),
key: 'featureRequest',
value: 'featureRequest',
},
];
const handleFeedbackChange = (
e: React.ChangeEvent<HTMLTextAreaElement>,
): void => {
setFeedback(e.target.value);
};
const handleContactSupportClick = useCallback((): void => {
handleContactSupport(isCloudUserVal);
}, [isCloudUserVal]);
return (
<div className="feedback-modal-container">
<div className="feedback-modal-header">
<Radio.Group
value={activeTab}
defaultValue={activeTab}
optionType="button"
className="feedback-modal-tabs"
options={items}
onChange={(e: RadioChangeEvent): void => setActiveTab(e.target.value)}
/>
</div>
<div className="feedback-modal-content">
<div className="feedback-modal-content-header">
<Input.TextArea
placeholder="Write your feedback here..."
rows={6}
required
className="feedback-input"
value={feedback}
onChange={handleFeedbackChange}
/>
</div>
</div>
<div className="feedback-modal-content-footer">
<Button
className="periscope-btn primary"
type="primary"
onClick={handleSubmit}
loading={isLoading}
disabled={feedback.length === 0}
>
Submit
</Button>
<div className="feedback-modal-content-footer-info-text">
<Typography.Text>
Have a specific issue?{' '}
<Typography.Link
className="contact-support-link"
onClick={handleContactSupportClick}
>
Contact Support{' '}
</Typography.Link>
or{' '}
<a
href="https://signoz.io/docs/introduction/"
target="_blank"
rel="noreferrer"
className="read-docs-link"
>
Read our docs
</a>
</Typography.Text>
</div>
</div>
</div>
);
}
export default FeedbackModal;

View File

@@ -0,0 +1,253 @@
.header-right-section-container {
display: flex;
align-items: center;
gap: 8px;
}
.share-modal-content,
.feedback-modal-container {
display: flex;
flex-direction: column;
gap: 16px;
padding: 12px;
width: 460px;
border-radius: 4px;
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
.absolute-relative-time-toggler-container {
display: flex;
gap: 8px;
align-items: center;
justify-content: space-between;
.absolute-relative-time-toggler-label {
color: var(--bg-vanilla-100);
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
.absolute-relative-time-toggler {
display: flex;
gap: 4px;
align-items: center;
}
.absolute-relative-time-error {
font-size: 12px;
color: var(--bg-amber-600);
}
.share-link {
.url-share-container {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
.url-share-container-header {
display: flex;
flex-direction: column;
gap: 4px;
.url-share-title,
.url-share-sub-title {
color: var(--bg-vanilla-100);
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.url-share-sub-title {
font-size: 12px;
color: var(--bg-vanilla-300);
font-weight: 400;
line-height: 18px;
letter-spacing: -0.06px;
}
}
}
}
}
.feedback-modal-container {
.feedback-modal-tabs {
width: 100%;
display: flex;
.ant-radio-button-wrapper {
flex: 1;
margin: 0px !important;
border: 1px solid var(--bg-slate-400);
&:before {
display: none;
}
.ant-radio-button-checked {
background-color: var(--bg-slate-400);
}
}
.feedback-modal-tab-label {
display: flex;
align-items: center;
gap: 8px;
.tab-icon {
width: 6px;
height: 6px;
}
.feedback-tab {
background-color: var(--bg-sakura-500);
}
.bug-tab {
background-color: var(--bg-amber-500);
}
.feature-tab {
background-color: var(--bg-robin-500);
}
}
.ant-tabs-nav-list {
.ant-tabs-tab {
padding: 6px 16px;
border-radius: 2px;
background: var(--bg-ink-400);
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
border: 1px solid var(--bg-slate-400);
margin: 0 !important;
.ant-tabs-tab-btn {
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 166.667% */
letter-spacing: -0.06px;
}
&-active {
background: var(--bg-slate-400);
color: var(--bg-vanilla-100);
border-bottom: none !important;
.ant-tabs-tab-btn {
color: var(--bg-vanilla-100);
}
}
}
}
}
.feedback-modal-content {
display: flex;
flex-direction: column;
gap: 16px;
.feedback-input {
resize: none;
text-area {
resize: none;
}
}
.feedback-content-include-console-logs {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
}
.feedback-modal-content-footer {
display: flex;
flex-direction: column;
gap: 16px;
.feedback-modal-content-footer-info-text {
font-size: 12px;
color: var(--bg-vanilla-400, #c0c1c3);
text-align: center;
/* button/ small */
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 24px; /* 200% */
.contact-support-link,
.read-docs-link {
color: var(--bg-robin-400);
font-weight: 500;
font-size: 12px;
}
}
}
}
.lightMode {
.share-modal-content,
.feedback-modal-container {
.absolute-relative-time-toggler-container {
.absolute-relative-time-toggler-label {
color: var(--bg-ink-400);
}
}
.share-link {
.url-share-container {
.url-share-container-header {
.url-share-title,
.url-share-sub-title {
color: var(--bg-ink-400);
}
.url-share-sub-title {
color: var(--bg-ink-300);
}
}
}
}
}
.feedback-modal-container {
.feedback-modal-tabs {
.ant-radio-button-wrapper {
flex: 1;
margin: 0px !important;
border: 1px solid var(--bg-vanilla-300);
&:before {
display: none;
}
.ant-radio-button-checked {
background-color: var(--bg-vanilla-300);
}
}
}
.feedback-modal-content-footer {
.feedback-modal-content-footer-info-text {
color: var(--bg-slate-400);
}
}
}
}

View File

@@ -0,0 +1,142 @@
import './HeaderRightSection.styles.scss';
import { Button, Popover } from 'antd';
import logEvent from 'api/common/logEvent';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { Globe, Inbox, SquarePen } from 'lucide-react';
import { useCallback, useState } from 'react';
import { useLocation } from 'react-router-dom';
import AnnouncementsModal from './AnnouncementsModal';
import FeedbackModal from './FeedbackModal';
import ShareURLModal from './ShareURLModal';
interface HeaderRightSectionProps {
enableAnnouncements: boolean;
enableShare: boolean;
enableFeedback: boolean;
}
function HeaderRightSection({
enableAnnouncements,
enableShare,
enableFeedback,
}: HeaderRightSectionProps): JSX.Element | null {
const location = useLocation();
const [openFeedbackModal, setOpenFeedbackModal] = useState(false);
const [openShareURLModal, setOpenShareURLModal] = useState(false);
const [openAnnouncementsModal, setOpenAnnouncementsModal] = useState(false);
const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
const handleOpenFeedbackModal = useCallback((): void => {
logEvent('Feedback: Clicked', {
page: location.pathname,
});
setOpenFeedbackModal(true);
setOpenShareURLModal(false);
setOpenAnnouncementsModal(false);
}, [location.pathname]);
const handleOpenShareURLModal = useCallback((): void => {
logEvent('Share: Clicked', {
page: location.pathname,
});
setOpenShareURLModal(true);
setOpenFeedbackModal(false);
setOpenAnnouncementsModal(false);
}, [location.pathname]);
const handleCloseFeedbackModal = (): void => {
setOpenFeedbackModal(false);
};
const handleOpenFeedbackModalChange = (open: boolean): void => {
setOpenFeedbackModal(open);
};
const handleOpenAnnouncementsModalChange = (open: boolean): void => {
setOpenAnnouncementsModal(open);
};
const handleOpenShareURLModalChange = (open: boolean): void => {
setOpenShareURLModal(open);
};
const isLicenseEnabled = isEnterpriseSelfHostedUser || isCloudUser;
return (
<div className="header-right-section-container">
{enableFeedback && isLicenseEnabled && (
<Popover
rootClassName="header-section-popover-root"
className="shareable-link-popover"
placement="bottomRight"
content={<FeedbackModal onClose={handleCloseFeedbackModal} />}
destroyTooltipOnHide
arrow={false}
trigger="click"
open={openFeedbackModal}
onOpenChange={handleOpenFeedbackModalChange}
>
<Button
className="share-feedback-btn periscope-btn ghost"
icon={<SquarePen size={14} />}
onClick={handleOpenFeedbackModal}
/>
</Popover>
)}
{enableAnnouncements && (
<Popover
rootClassName="header-section-popover-root"
className="shareable-link-popover"
placement="bottomRight"
content={<AnnouncementsModal />}
arrow={false}
destroyTooltipOnHide
trigger="click"
open={openAnnouncementsModal}
onOpenChange={handleOpenAnnouncementsModalChange}
>
<Button
icon={<Inbox size={14} />}
className="periscope-btn ghost announcements-btn"
onClick={(): void => {
logEvent('Announcements: Clicked', {
page: location.pathname,
});
}}
/>
</Popover>
)}
{enableShare && (
<Popover
rootClassName="header-section-popover-root"
className="shareable-link-popover"
placement="bottomRight"
content={<ShareURLModal />}
open={openShareURLModal}
destroyTooltipOnHide
arrow={false}
trigger="click"
onOpenChange={handleOpenShareURLModalChange}
>
<Button
className="share-link-btn periscope-btn ghost"
icon={<Globe size={14} />}
onClick={handleOpenShareURLModal}
>
Share
</Button>
</Popover>
)}
</div>
);
}
export default HeaderRightSection;

View File

@@ -0,0 +1,171 @@
import { Color } from '@signozhq/design-tokens';
import { Button, Switch, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import useUrlQuery from 'hooks/useUrlQuery';
import GetMinMax from 'lib/getMinMax';
import { Check, Info, Link2 } from 'lucide-react';
import { useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
const routesToBeSharedWithTime = [
ROUTES.LOGS_EXPLORER,
ROUTES.TRACES_EXPLORER,
ROUTES.METRICS_EXPLORER_EXPLORER,
ROUTES.METER_EXPLORER,
];
function ShareURLModal(): JSX.Element {
const urlQuery = useUrlQuery();
const location = useLocation();
const { selectedTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const [enableAbsoluteTime, setEnableAbsoluteTime] = useState(
selectedTime !== 'custom',
);
const startTime = urlQuery.get(QueryParams.startTime);
const endTime = urlQuery.get(QueryParams.endTime);
const relativeTime = urlQuery.get(QueryParams.relativeTime);
const [isURLCopied, setIsURLCopied] = useState(false);
const [, handleCopyToClipboard] = useCopyToClipboard();
const isValidateRelativeTime = useMemo(
() =>
selectedTime !== 'custom' ||
(startTime && endTime && selectedTime === 'custom'),
[startTime, endTime, selectedTime],
);
const shareURLWithTime = useMemo(
() => relativeTime || (startTime && endTime),
[relativeTime, startTime, endTime],
);
const isRouteToBeSharedWithTime = useMemo(
() =>
routesToBeSharedWithTime.some((route) =>
matchPath(location.pathname, { path: route, exact: true }),
),
[location.pathname],
);
// eslint-disable-next-line sonarjs/cognitive-complexity
const processURL = (): string => {
let currentUrl = window.location.href;
const isCustomTime = !!(startTime && endTime && selectedTime === 'custom');
if (shareURLWithTime || isRouteToBeSharedWithTime) {
if (enableAbsoluteTime || isCustomTime) {
if (selectedTime === 'custom') {
if (startTime && endTime) {
urlQuery.set(QueryParams.startTime, startTime.toString());
urlQuery.set(QueryParams.endTime, endTime.toString());
}
} else {
const { minTime, maxTime } = GetMinMax(selectedTime);
urlQuery.set(QueryParams.startTime, minTime.toString());
urlQuery.set(QueryParams.endTime, maxTime.toString());
}
urlQuery.delete(QueryParams.relativeTime);
currentUrl = `${window.location.origin}${
location.pathname
}?${urlQuery.toString()}`;
} else {
urlQuery.delete(QueryParams.startTime);
urlQuery.delete(QueryParams.endTime);
urlQuery.set(QueryParams.relativeTime, selectedTime);
currentUrl = `${window.location.origin}${
location.pathname
}?${urlQuery.toString()}`;
}
}
return currentUrl;
};
const handleCopyURL = (): void => {
const URL = processURL();
handleCopyToClipboard(URL);
setIsURLCopied(true);
logEvent('Share: Copy link clicked', {
page: location.pathname,
URL,
});
setTimeout(() => {
setIsURLCopied(false);
}, 1000);
};
return (
<div className="share-modal-content">
{(shareURLWithTime || isRouteToBeSharedWithTime) && (
<>
<div className="absolute-relative-time-toggler-container">
<Typography.Text className="absolute-relative-time-toggler-label">
Enable absolute time
</Typography.Text>
<div className="absolute-relative-time-toggler">
{!isValidateRelativeTime && (
<Info size={14} color={Color.BG_AMBER_600} />
)}
<Switch
checked={enableAbsoluteTime}
disabled={!isValidateRelativeTime}
size="small"
onChange={(): void => {
setEnableAbsoluteTime((prev) => !prev);
}}
/>
</div>
</div>
{!isValidateRelativeTime && (
<div className="absolute-relative-time-error">
Please select / enter valid relative time to toggle.
</div>
)}
</>
)}
<div className="share-link">
<div className="url-share-container">
<div className="url-share-container-header">
<Typography.Text className="url-share-title">
Share page link
</Typography.Text>
<Typography.Text className="url-share-sub-title">
Share the current page link with your team member
</Typography.Text>
</div>
<Button
className="periscope-btn secondary"
onClick={handleCopyURL}
icon={isURLCopied ? <Check size={14} /> : <Link2 size={14} />}
>
Copy page link
</Button>
</div>
</div>
</div>
);
}
export default ShareURLModal;

View File

@@ -0,0 +1,29 @@
import { render, screen } from '@testing-library/react';
import AnnouncementsModal from '../AnnouncementsModal';
describe('AnnouncementsModal', () => {
it('should render announcements modal with title', () => {
render(<AnnouncementsModal />);
expect(screen.getByText('Announcements')).toBeInTheDocument();
});
it('should have proper structure and classes', () => {
render(<AnnouncementsModal />);
const container = screen
.getByText('Announcements')
.closest('.announcements-modal-container');
expect(container).toBeInTheDocument();
const headerContainer = screen
.getByText('Announcements')
.closest('.announcements-modal-container-header');
expect(headerContainer).toBeInTheDocument();
});
it('should render without any errors', () => {
expect(() => render(<AnnouncementsModal />)).not.toThrow();
});
});

View File

@@ -0,0 +1,274 @@
/* eslint-disable sonarjs/no-duplicate-string */
// Mock dependencies before imports
import { toast } from '@signozhq/sonner';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import logEvent from 'api/common/logEvent';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { handleContactSupport } from 'pages/Integrations/utils';
import { useLocation } from 'react-router-dom';
import FeedbackModal from '../FeedbackModal';
jest.mock('api/common/logEvent', () => ({
__esModule: true,
default: jest.fn(() => Promise.resolve()),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: jest.fn(),
}));
jest.mock('@signozhq/sonner', () => ({
toast: {
success: jest.fn(),
error: jest.fn(),
},
}));
jest.mock('hooks/useGetTenantLicense', () => ({
useGetTenantLicense: jest.fn(),
}));
jest.mock('pages/Integrations/utils', () => ({
handleContactSupport: jest.fn(),
}));
const mockLogEvent = logEvent as jest.MockedFunction<typeof logEvent>;
const mockUseLocation = useLocation as jest.Mock;
const mockUseGetTenantLicense = useGetTenantLicense as jest.Mock;
const mockHandleContactSupport = handleContactSupport as jest.Mock;
const mockToast = toast as jest.Mocked<typeof toast>;
const mockOnClose = jest.fn();
const mockLocation = {
pathname: '/test-path',
};
describe('FeedbackModal', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseLocation.mockReturnValue(mockLocation);
mockUseGetTenantLicense.mockReturnValue({
isCloudUser: false,
});
mockToast.success.mockClear();
mockToast.error.mockClear();
});
it('should render feedback modal with all tabs', () => {
render(<FeedbackModal onClose={mockOnClose} />);
expect(screen.getByText('Feedback')).toBeInTheDocument();
expect(screen.getByText('Report a bug')).toBeInTheDocument();
expect(screen.getByText('Feature request')).toBeInTheDocument();
expect(
screen.getByPlaceholderText('Write your feedback here...'),
).toBeInTheDocument();
expect(screen.getByRole('button', { name: /submit/i })).toBeInTheDocument();
});
it('should switch between tabs when clicked', async () => {
const user = userEvent.setup();
render(<FeedbackModal onClose={mockOnClose} />);
// Initially, feedback radio should be active
const feedbackRadio = screen.getByRole('radio', { name: 'Feedback' });
expect(feedbackRadio).toBeChecked();
const bugTab = screen.getByText('Report a bug');
await user.click(bugTab);
// Bug radio should now be active
const bugRadio = screen.getByRole('radio', { name: 'Report a bug' });
expect(bugRadio).toBeChecked();
const featureTab = screen.getByText('Feature request');
await user.click(featureTab);
// Feature radio should now be active
const featureRadio = screen.getByRole('radio', { name: 'Feature request' });
expect(featureRadio).toBeChecked();
});
it('should update feedback text when typing in textarea', async () => {
const user = userEvent.setup();
render(<FeedbackModal onClose={mockOnClose} />);
const textarea = screen.getByPlaceholderText('Write your feedback here...');
const testFeedback = 'This is my feedback';
await user.type(textarea, testFeedback);
expect(textarea).toHaveValue(testFeedback);
});
it('should submit feedback and log event when submit button is clicked', async () => {
const user = userEvent.setup();
render(<FeedbackModal onClose={mockOnClose} />);
const textarea = screen.getByPlaceholderText('Write your feedback here...');
const submitButton = screen.getByRole('button', { name: /submit/i });
const testFeedback = 'Test feedback content';
await user.type(textarea, testFeedback);
await user.click(submitButton);
expect(mockLogEvent).toHaveBeenCalledWith('Feedback: Submitted', {
data: testFeedback,
type: 'feedback',
page: mockLocation.pathname,
});
expect(mockOnClose).toHaveBeenCalled();
expect(mockToast.success).toHaveBeenCalledWith(
'Feedback submitted successfully',
{
position: 'top-right',
},
);
});
it('should submit bug report with correct type', async () => {
const user = userEvent.setup();
render(<FeedbackModal onClose={mockOnClose} />);
// Switch to bug report tab
const bugTab = screen.getByText('Report a bug');
await user.click(bugTab);
// Verify bug report radio is now active
const bugRadio = screen.getByRole('radio', { name: 'Report a bug' });
expect(bugRadio).toBeChecked();
const textarea = screen.getByPlaceholderText('Write your feedback here...');
const submitButton = screen.getByRole('button', { name: /submit/i });
const testFeedback = 'This is a bug report';
await user.type(textarea, testFeedback);
await user.click(submitButton);
expect(mockLogEvent).toHaveBeenCalledWith('Feedback: Submitted', {
data: testFeedback,
type: 'reportBug',
page: mockLocation.pathname,
});
expect(mockOnClose).toHaveBeenCalled();
expect(mockToast.success).toHaveBeenCalledWith(
'Bug report submitted successfully',
{
position: 'top-right',
},
);
});
it('should submit feature request with correct type', async () => {
const user = userEvent.setup();
render(<FeedbackModal onClose={mockOnClose} />);
// Switch to feature request tab
const featureTab = screen.getByText('Feature request');
await user.click(featureTab);
// Verify feature request radio is now active
const featureRadio = screen.getByRole('radio', { name: 'Feature request' });
expect(featureRadio).toBeChecked();
const textarea = screen.getByPlaceholderText('Write your feedback here...');
const submitButton = screen.getByRole('button', { name: /submit/i });
const testFeedback = 'This is a feature request';
await user.type(textarea, testFeedback);
await user.click(submitButton);
expect(mockLogEvent).toHaveBeenCalledWith('Feedback: Submitted', {
data: testFeedback,
type: 'featureRequest',
page: mockLocation.pathname,
});
expect(mockOnClose).toHaveBeenCalled();
expect(mockToast.success).toHaveBeenCalledWith(
'Feature request submitted successfully',
{
position: 'top-right',
},
);
});
it('should call handleContactSupport when contact support link is clicked', async () => {
const user = userEvent.setup();
const isCloudUser = true;
mockUseGetTenantLicense.mockReturnValue({
isCloudUser,
});
render(<FeedbackModal onClose={mockOnClose} />);
const contactSupportLink = screen.getByText('Contact Support');
await user.click(contactSupportLink);
expect(mockHandleContactSupport).toHaveBeenCalledWith(isCloudUser);
});
it('should handle non-cloud user for contact support', async () => {
const user = userEvent.setup();
const isCloudUser = false;
mockUseGetTenantLicense.mockReturnValue({
isCloudUser,
});
render(<FeedbackModal onClose={mockOnClose} />);
const contactSupportLink = screen.getByText('Contact Support');
await user.click(contactSupportLink);
expect(mockHandleContactSupport).toHaveBeenCalledWith(isCloudUser);
});
it('should render docs link with correct attributes', () => {
render(<FeedbackModal onClose={mockOnClose} />);
const docsLink = screen.getByText('Read our docs');
expect(docsLink).toHaveAttribute(
'href',
'https://signoz.io/docs/introduction/',
);
expect(docsLink).toHaveAttribute('target', '_blank');
expect(docsLink).toHaveAttribute('rel', 'noreferrer');
});
it('should reset form state when component unmounts', async () => {
const user = userEvent.setup();
// Render component
const { unmount } = render(<FeedbackModal onClose={mockOnClose} />);
// Change the form state first
const textArea = screen.getByPlaceholderText('Write your feedback here...');
await user.type(textArea, 'Some feedback text');
// Change the active tab
const bugTab = screen.getByText('Report a bug');
await user.click(bugTab);
// Verify state has changed
expect(textArea).toHaveValue('Some feedback text');
// Unmount the component - this should trigger cleanup
unmount();
// Re-render the component to verify state was reset
render(<FeedbackModal onClose={mockOnClose} />);
// Verify form state is reset
const newTextArea = screen.getByPlaceholderText(
'Write your feedback here...',
);
expect(newTextArea).toHaveValue(''); // Should be empty
// Verify active radio is reset to default (Feedback radio)
const feedbackRadio = screen.getByRole('radio', { name: 'Feedback' });
expect(feedbackRadio).toBeChecked();
});
});

View File

@@ -0,0 +1,285 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable react/jsx-props-no-spreading */
// Mock dependencies before imports
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import logEvent from 'api/common/logEvent';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { useLocation } from 'react-router-dom';
import HeaderRightSection from '../HeaderRightSection';
jest.mock('api/common/logEvent', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: jest.fn(),
}));
jest.mock('../FeedbackModal', () => ({
__esModule: true,
default: ({ onClose }: { onClose: () => void }): JSX.Element => (
<div data-testid="feedback-modal">
<button onClick={onClose} type="button">
Close Feedback
</button>
</div>
),
}));
jest.mock('../ShareURLModal', () => ({
__esModule: true,
default: (): JSX.Element => (
<div data-testid="share-modal">Share URL Modal</div>
),
}));
jest.mock('../AnnouncementsModal', () => ({
__esModule: true,
default: (): JSX.Element => (
<div data-testid="announcements-modal">Announcements Modal</div>
),
}));
jest.mock('hooks/useGetTenantLicense', () => ({
useGetTenantLicense: jest.fn(),
}));
const mockLogEvent = logEvent as jest.Mock;
const mockUseLocation = useLocation as jest.Mock;
const mockUseGetTenantLicense = useGetTenantLicense as jest.Mock;
const defaultProps = {
enableAnnouncements: true,
enableShare: true,
enableFeedback: true,
};
const mockLocation = {
pathname: '/test-path',
};
describe('HeaderRightSection', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseLocation.mockReturnValue(mockLocation);
// Default to licensed user (Enterprise or Cloud)
mockUseGetTenantLicense.mockReturnValue({
isCloudUser: true,
isEnterpriseSelfHostedUser: false,
isCommunityUser: false,
isCommunityEnterpriseUser: false,
});
});
it('should render all buttons when all features are enabled', () => {
render(<HeaderRightSection {...defaultProps} />);
const buttons = screen.getAllByRole('button');
expect(buttons).toHaveLength(3);
expect(screen.getByRole('button', { name: /share/i })).toBeInTheDocument();
// Check for feedback button by class
const feedbackButton = document.querySelector(
'.share-feedback-btn[class*="share-feedback-btn"]',
);
expect(feedbackButton).toBeInTheDocument();
// Check for announcements button by finding the inbox icon
const inboxIcon = document.querySelector('.lucide-inbox');
expect(inboxIcon).toBeInTheDocument();
});
it('should render only enabled features', () => {
render(
<HeaderRightSection
enableAnnouncements={false}
enableShare={false}
enableFeedback
/>,
);
const buttons = screen.getAllByRole('button');
expect(buttons).toHaveLength(1);
expect(
screen.queryByRole('button', { name: /share/i }),
).not.toBeInTheDocument();
// Check that inbox icon is not present
const inboxIcon = document.querySelector('.lucide-inbox');
expect(inboxIcon).not.toBeInTheDocument();
// Check that feedback button is present
const squarePenIcon = document.querySelector('.lucide-square-pen');
expect(squarePenIcon).toBeInTheDocument();
});
it('should open feedback modal and log event when feedback button is clicked', async () => {
const user = userEvent.setup();
render(<HeaderRightSection {...defaultProps} />);
const feedbackButton = document
.querySelector('.lucide-square-pen')
?.closest('button');
expect(feedbackButton).toBeInTheDocument();
await user.click(feedbackButton!);
expect(mockLogEvent).toHaveBeenCalledWith('Feedback: Clicked', {
page: mockLocation.pathname,
});
expect(screen.getByTestId('feedback-modal')).toBeInTheDocument();
});
it('should open share modal and log event when share button is clicked', async () => {
const user = userEvent.setup();
render(<HeaderRightSection {...defaultProps} />);
const shareButton = screen.getByRole('button', { name: /share/i });
await user.click(shareButton);
expect(mockLogEvent).toHaveBeenCalledWith('Share: Clicked', {
page: mockLocation.pathname,
});
expect(screen.getByTestId('share-modal')).toBeInTheDocument();
});
it('should log event when announcements button is clicked', async () => {
const user = userEvent.setup();
render(<HeaderRightSection {...defaultProps} />);
const announcementsButton = document
.querySelector('.lucide-inbox')
?.closest('button');
expect(announcementsButton).toBeInTheDocument();
await user.click(announcementsButton!);
expect(mockLogEvent).toHaveBeenCalledWith('Announcements: Clicked', {
page: mockLocation.pathname,
});
});
it('should close feedback modal when onClose is called', async () => {
const user = userEvent.setup();
render(<HeaderRightSection {...defaultProps} />);
// Open feedback modal
const feedbackButton = document
.querySelector('.lucide-square-pen')
?.closest('button');
expect(feedbackButton).toBeInTheDocument();
await user.click(feedbackButton!);
expect(screen.getByTestId('feedback-modal')).toBeInTheDocument();
// Close feedback modal
const closeFeedbackButton = screen.getByText('Close Feedback');
await user.click(closeFeedbackButton);
expect(screen.queryByTestId('feedback-modal')).not.toBeInTheDocument();
});
it('should close other modals when opening feedback modal', async () => {
const user = userEvent.setup();
render(<HeaderRightSection {...defaultProps} />);
// Open share modal first
const shareButton = screen.getByRole('button', { name: /share/i });
await user.click(shareButton);
expect(screen.getByTestId('share-modal')).toBeInTheDocument();
// Open feedback modal - should close share modal
const feedbackButton = document
.querySelector('.lucide-square-pen')
?.closest('button');
expect(feedbackButton).toBeInTheDocument();
await user.click(feedbackButton!);
expect(screen.getByTestId('feedback-modal')).toBeInTheDocument();
expect(screen.queryByTestId('share-modal')).not.toBeInTheDocument();
});
it('should show feedback button for Cloud users when feedback is enabled', () => {
mockUseGetTenantLicense.mockReturnValue({
isCloudUser: true,
isEnterpriseSelfHostedUser: false,
isCommunityUser: false,
isCommunityEnterpriseUser: false,
});
render(<HeaderRightSection {...defaultProps} />);
const feedbackButton = document.querySelector('.lucide-square-pen');
expect(feedbackButton).toBeInTheDocument();
});
it('should show feedback button for Enterprise self-hosted users when feedback is enabled', () => {
mockUseGetTenantLicense.mockReturnValue({
isCloudUser: false,
isEnterpriseSelfHostedUser: true,
isCommunityUser: false,
isCommunityEnterpriseUser: false,
});
render(<HeaderRightSection {...defaultProps} />);
const feedbackButton = document.querySelector('.lucide-square-pen');
expect(feedbackButton).toBeInTheDocument();
});
it('should hide feedback button for Community users even when feedback is enabled', () => {
mockUseGetTenantLicense.mockReturnValue({
isCloudUser: false,
isEnterpriseSelfHostedUser: false,
isCommunityUser: true,
isCommunityEnterpriseUser: false,
});
render(<HeaderRightSection {...defaultProps} />);
const feedbackButton = document.querySelector('.lucide-square-pen');
expect(feedbackButton).not.toBeInTheDocument();
});
it('should hide feedback button for Community Enterprise users even when feedback is enabled', () => {
mockUseGetTenantLicense.mockReturnValue({
isCloudUser: false,
isEnterpriseSelfHostedUser: false,
isCommunityUser: false,
isCommunityEnterpriseUser: true,
});
render(<HeaderRightSection {...defaultProps} />);
const feedbackButton = document.querySelector('.lucide-square-pen');
expect(feedbackButton).not.toBeInTheDocument();
});
it('should render correct number of buttons when feedback is hidden due to license', () => {
mockUseGetTenantLicense.mockReturnValue({
isCloudUser: false,
isEnterpriseSelfHostedUser: false,
isCommunityUser: true,
isCommunityEnterpriseUser: false,
});
render(<HeaderRightSection {...defaultProps} />);
// Should have 2 buttons (announcements + share) instead of 3
const buttons = screen.getAllByRole('button');
expect(buttons).toHaveLength(2);
// Verify which buttons are present
expect(screen.getByRole('button', { name: /share/i })).toBeInTheDocument();
const inboxIcon = document.querySelector('.lucide-inbox');
expect(inboxIcon).toBeInTheDocument();
// Verify feedback button is not present
const feedbackIcon = document.querySelector('.lucide-square-pen');
expect(feedbackIcon).not.toBeInTheDocument();
});
});

View File

@@ -0,0 +1,289 @@
// Mock dependencies before imports
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import useUrlQuery from 'hooks/useUrlQuery';
import GetMinMax from 'lib/getMinMax';
import { useSelector } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useCopyToClipboard } from 'react-use';
import ShareURLModal from '../ShareURLModal';
jest.mock('api/common/logEvent', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: jest.fn(),
matchPath: jest.fn(),
}));
jest.mock('hooks/useUrlQuery', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest.fn(),
}));
jest.mock('lib/getMinMax', () => ({
__esModule: true,
default: jest.fn(),
}));
jest.mock('react-use', () => ({
...jest.requireActual('react-use'),
useCopyToClipboard: jest.fn(),
}));
// Mock window.location
const mockLocation = {
href: 'https://example.com/test-path?param=value',
origin: 'https://example.com',
};
Object.defineProperty(window, 'location', {
value: mockLocation,
writable: true,
});
const mockLogEvent = logEvent as jest.Mock;
const mockUseLocation = useLocation as jest.Mock;
const mockUseUrlQuery = useUrlQuery as jest.Mock;
const mockUseSelector = useSelector as jest.Mock;
const mockGetMinMax = GetMinMax as jest.Mock;
const mockUseCopyToClipboard = useCopyToClipboard as jest.Mock;
const mockMatchPath = matchPath as jest.Mock;
const mockUrlQuery = {
get: jest.fn(),
set: jest.fn(),
delete: jest.fn(),
toString: jest.fn(() => 'param=value'),
};
const mockHandleCopyToClipboard = jest.fn();
const TEST_PATH = '/test-path';
const ENABLE_ABSOLUTE_TIME_TEXT = 'Enable absolute time';
describe('ShareURLModal', () => {
beforeEach(() => {
jest.clearAllMocks();
mockUseLocation.mockReturnValue({
pathname: TEST_PATH,
});
mockUseUrlQuery.mockReturnValue(mockUrlQuery);
mockUseSelector.mockReturnValue({
selectedTime: '5min',
});
mockGetMinMax.mockReturnValue({
minTime: 1000000,
maxTime: 2000000,
});
mockUseCopyToClipboard.mockReturnValue([null, mockHandleCopyToClipboard]);
mockMatchPath.mockReturnValue(false);
// Reset URL query mocks - all return null by default
mockUrlQuery.get.mockReturnValue(null);
// Reset mock functions
mockUrlQuery.set.mockClear();
mockUrlQuery.delete.mockClear();
mockUrlQuery.toString.mockReturnValue('param=value');
});
it('should render share modal with copy button', () => {
render(<ShareURLModal />);
expect(screen.getByText('Share page link')).toBeInTheDocument();
expect(
screen.getByText('Share the current page link with your team member'),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: /copy page link/i }),
).toBeInTheDocument();
});
it('should copy URL and log event when copy button is clicked', async () => {
const user = userEvent.setup();
render(<ShareURLModal />);
const copyButton = screen.getByRole('button', { name: /copy page link/i });
await user.click(copyButton);
expect(mockHandleCopyToClipboard).toHaveBeenCalled();
expect(mockLogEvent).toHaveBeenCalledWith('Share: Copy link clicked', {
page: TEST_PATH,
URL: expect.any(String),
});
});
it('should show absolute time toggle when on time-enabled route', () => {
mockMatchPath.mockReturnValue(true); // Simulate being on a route that supports time
render(<ShareURLModal />);
expect(screen.getByText(ENABLE_ABSOLUTE_TIME_TEXT)).toBeInTheDocument();
expect(screen.getByRole('switch')).toBeInTheDocument();
});
it('should show absolute time toggle when URL has time parameters', () => {
mockUrlQuery.get.mockImplementation((key: string) =>
key === 'relativeTime' ? '5min' : null,
);
render(<ShareURLModal />);
expect(screen.getByText(ENABLE_ABSOLUTE_TIME_TEXT)).toBeInTheDocument();
});
it('should toggle absolute time switch', async () => {
const user = userEvent.setup();
mockMatchPath.mockReturnValue(true);
mockUseSelector.mockReturnValue({
selectedTime: '5min', // Non-custom time should enable absolute time by default
});
render(<ShareURLModal />);
const toggleSwitch = screen.getByRole('switch');
// Should be checked by default for non-custom time
expect(toggleSwitch).toBeChecked();
await user.click(toggleSwitch);
expect(toggleSwitch).not.toBeChecked();
});
it('should disable toggle when relative time is invalid', () => {
mockUseSelector.mockReturnValue({
selectedTime: 'custom',
});
// Invalid - missing start and end time for custom
mockUrlQuery.get.mockReturnValue(null);
mockMatchPath.mockReturnValue(true);
render(<ShareURLModal />);
expect(
screen.getByText('Please select / enter valid relative time to toggle.'),
).toBeInTheDocument();
expect(screen.getByRole('switch')).toBeDisabled();
});
it('should process URL with absolute time for non-custom time', async () => {
const user = userEvent.setup();
mockMatchPath.mockReturnValue(true);
mockUseSelector.mockReturnValue({
selectedTime: '5min',
});
render(<ShareURLModal />);
// Absolute time should be enabled by default for non-custom time
// Click copy button directly
const copyButton = screen.getByRole('button', { name: /copy page link/i });
await user.click(copyButton);
expect(mockUrlQuery.set).toHaveBeenCalledWith('startTime', '1000000');
expect(mockUrlQuery.set).toHaveBeenCalledWith('endTime', '2000000');
expect(mockUrlQuery.delete).toHaveBeenCalledWith('relativeTime');
});
it('should process URL with custom time parameters', async () => {
const user = userEvent.setup();
mockMatchPath.mockReturnValue(true);
mockUseSelector.mockReturnValue({
selectedTime: 'custom',
});
mockUrlQuery.get.mockImplementation((key: string) => {
switch (key) {
case 'startTime':
return '1500000';
case 'endTime':
return '1600000';
default:
return null;
}
});
render(<ShareURLModal />);
// Should be enabled by default for custom time
const copyButton = screen.getByRole('button', { name: /copy page link/i });
await user.click(copyButton);
expect(mockUrlQuery.set).toHaveBeenCalledWith('startTime', '1500000');
expect(mockUrlQuery.set).toHaveBeenCalledWith('endTime', '1600000');
});
it('should process URL with relative time when absolute time is disabled', async () => {
const user = userEvent.setup();
mockMatchPath.mockReturnValue(true);
mockUseSelector.mockReturnValue({
selectedTime: '5min',
});
render(<ShareURLModal />);
// Disable absolute time first (it's enabled by default for non-custom time)
const toggleSwitch = screen.getByRole('switch');
await user.click(toggleSwitch);
const copyButton = screen.getByRole('button', { name: /copy page link/i });
await user.click(copyButton);
expect(mockUrlQuery.delete).toHaveBeenCalledWith('startTime');
expect(mockUrlQuery.delete).toHaveBeenCalledWith('endTime');
expect(mockUrlQuery.set).toHaveBeenCalledWith('relativeTime', '5min');
});
it('should handle routes that should be shared with time', async () => {
const user = userEvent.setup();
mockUseLocation.mockReturnValue({
pathname: ROUTES.LOGS_EXPLORER,
});
mockMatchPath.mockImplementation(
(pathname: string, options: any) => options.path === ROUTES.LOGS_EXPLORER,
);
render(<ShareURLModal />);
expect(screen.getByText(ENABLE_ABSOLUTE_TIME_TEXT)).toBeInTheDocument();
expect(screen.getByRole('switch')).toBeChecked();
// on clicking copy page link, the copied url should have startTime and endTime
const copyButton = screen.getByRole('button', { name: /copy page link/i });
await user.click(copyButton);
expect(mockUrlQuery.set).toHaveBeenCalledWith('startTime', '1000000');
expect(mockUrlQuery.set).toHaveBeenCalledWith('endTime', '2000000');
expect(mockUrlQuery.delete).toHaveBeenCalledWith('relativeTime');
// toggle the switch to share url with relative time
const toggleSwitch = screen.getByRole('switch');
await user.click(toggleSwitch);
await user.click(copyButton);
expect(mockUrlQuery.delete).toHaveBeenCalledWith('startTime');
expect(mockUrlQuery.delete).toHaveBeenCalledWith('endTime');
expect(mockUrlQuery.set).toHaveBeenCalledWith('relativeTime', '5min');
});
});

View File

@@ -125,6 +125,7 @@ export const getHostTracesQueryPayload = (
},
],
queryFormulas: [],
queryTraceOperator: [],
},
id: '572f1d91-6ac0-46c0-b726-c21488b34434',
queryType: EQueryType.QUERY_BUILDER,

View File

@@ -169,6 +169,7 @@
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
}
.ant-drawer-close {
padding: 0px;
}

View File

@@ -51,6 +51,7 @@ export const getHostLogsQueryPayload = (
},
],
queryFormulas: [],
queryTraceOperator: [],
},
id: uuidv4(),
queryType: EQueryType.QUERY_BUILDER,

View File

@@ -78,7 +78,7 @@ function Metrics({
signal,
}: QueryFunctionContext): Promise<
SuccessResponse<MetricRangePayloadProps>
> => GetMetricQueryRange(payload, ENTITY_VERSION_V4, signal),
> => GetMetricQueryRange(payload, ENTITY_VERSION_V4, undefined, signal),
enabled: !!payload && visibilities[index],
keepPreviousData: true,
})),

View File

@@ -49,6 +49,7 @@ function InputWithLabel({
value={inputValue}
onChange={handleChange}
name={label.toLowerCase()}
data-testid={`input-${label}`}
/>
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
{onClose && (

View File

@@ -23,6 +23,7 @@ import {
} from 'container/LogDetailedView/utils';
import useInitialQuery from 'container/LogsExplorerContext/useInitialQuery';
import { useOptionsMenu } from 'container/OptionsMenu';
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
@@ -39,7 +40,7 @@ import {
TextSelect,
X,
} from 'lucide-react';
import { useMemo, useState } from 'react';
import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { useCopyToClipboard, useLocation } from 'react-use';
import { AppState } from 'store/reducers';
@@ -94,6 +95,8 @@ function LogDetailInner({
const { notifications } = useNotifications();
const { onLogCopy } = useCopyLogLink(log?.id);
const LogJsonData = log ? aggregateAttributesResourcesToString(log) : '';
const handleModeChange = (e: RadioChangeEvent): void => {
@@ -146,6 +149,34 @@ function LogDetailInner({
safeNavigate(`${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`);
};
const handleQueryExpressionChange = useCallback(
(value: string, queryIndex: number) => {
// update the query at the given index
setContextQuery((prev) => {
if (!prev) return prev;
return {
...prev,
builder: {
...prev.builder,
queryData: prev.builder.queryData.map((query, idx) =>
idx === queryIndex
? {
...query,
filter: {
...query.filter,
expression: value,
},
}
: query,
),
},
};
});
},
[],
);
const handleRunQuery = (expression: string): void => {
let updatedContextQuery = cloneDeep(contextQuery);
@@ -305,11 +336,19 @@ function LogDetailInner({
onClick={handleFilterVisible}
/>
)}
<Tooltip title="Copy Log Link" placement="left" aria-label="Copy Log Link">
<Button
className="action-btn"
icon={<Copy size={16} />}
onClick={onLogCopy}
/>
</Tooltip>
</div>
{isFilterVisible && contextQuery?.builder.queryData[0] && (
<div className="log-detail-drawer-query-container">
<QuerySearch
onChange={(): void => {}}
onChange={(value): void => handleQueryExpressionChange(value, 0)}
dataSource={DataSource.LOGS}
queryData={contextQuery?.builder.queryData[0]}
onRun={handleRunQuery}

View File

@@ -208,7 +208,11 @@ function ListLogView({
fontSize={fontSize}
>
<div className="log-line">
<LogStateIndicator type={logType} fontSize={fontSize} />
<LogStateIndicator
fontSize={fontSize}
severityText={logData.severity_text}
severityNumber={logData.severity_number}
/>
<div>
<LogContainer fontSize={fontSize}>
{updatedSelecedFields.some((field) => field.name === 'body') && (

View File

@@ -7,7 +7,6 @@
height: 100%;
width: 3px;
border-radius: 50px;
background-color: transparent;
&.small {
min-height: 16px;
@@ -21,24 +20,107 @@
min-height: 24px;
}
&.INFO {
background-color: var(--bg-robin-500);
// Severity variant CSS classes using design tokens
// Trace variants -
&.severity-trace-0 {
background-color: var(--bg-forest-600);
}
&.WARNING,
&.WARN {
background-color: var(--bg-amber-500);
&.severity-trace-1 {
background-color: var(--bg-forest-500);
}
&.ERROR {
background-color: var(--bg-cherry-500);
}
&.TRACE {
&.severity-trace-2 {
background-color: var(--bg-forest-400);
}
&.DEBUG {
&.severity-trace-3 {
background-color: var(--bg-forest-300);
}
&.severity-trace-4 {
background-color: var(--bg-forest-200);
}
// Debug variants
&.severity-debug-0 {
background-color: var(--bg-aqua-600);
}
&.severity-debug-1 {
background-color: var(--bg-aqua-500);
}
&.FATAL {
&.severity-debug-2 {
background-color: var(--bg-aqua-400);
}
&.severity-debug-3 {
background-color: var(--bg-aqua-300);
}
&.severity-debug-4 {
background-color: var(--bg-aqua-200);
}
// Info variants
&.severity-info-0 {
background-color: var(--bg-robin-600);
}
&.severity-info-1 {
background-color: var(--bg-robin-500);
}
&.severity-info-2 {
background-color: var(--bg-robin-400);
}
&.severity-info-3 {
background-color: var(--bg-robin-300);
}
&.severity-info-4 {
background-color: var(--bg-robin-200);
}
// Warn variants
&.severity-warn-0 {
background-color: var(--bg-amber-600);
}
&.severity-warn-1 {
background-color: var(--bg-amber-500);
}
&.severity-warn-2 {
background-color: var(--bg-amber-400);
}
&.severity-warn-3 {
background-color: var(--bg-amber-300);
}
&.severity-warn-4 {
background-color: var(--bg-amber-200);
}
// Error variants
&.severity-error-0 {
background-color: var(--bg-cherry-600);
}
&.severity-error-1 {
background-color: var(--bg-cherry-500);
}
&.severity-error-2 {
background-color: var(--bg-cherry-400);
}
&.severity-error-3 {
background-color: var(--bg-cherry-300);
}
&.severity-error-4 {
background-color: var(--bg-cherry-200);
}
// Fatal variants
&.severity-fatal-0 {
background-color: var(--bg-sakura-600);
}
&.severity-fatal-1 {
background-color: var(--bg-sakura-500);
}
&.severity-fatal-2 {
background-color: var(--bg-sakura-400);
}
&.severity-fatal-3 {
background-color: var(--bg-sakura-300);
}
&.severity-fatal-4 {
background-color: var(--bg-sakura-200);
}
}
}

View File

@@ -6,37 +6,41 @@ import LogStateIndicator from './LogStateIndicator';
describe('LogStateIndicator', () => {
it('renders correctly with default props', () => {
const { container } = render(
<LogStateIndicator type="INFO" fontSize={FontSize.MEDIUM} />,
<LogStateIndicator severityText="INFO" fontSize={FontSize.MEDIUM} />,
);
const indicator = container.firstChild as HTMLElement;
expect(indicator.classList.contains('log-state-indicator')).toBe(true);
expect(indicator.classList.contains('isActive')).toBe(false);
expect(container.querySelector('.line')).toBeTruthy();
expect(container.querySelector('.line')?.classList.contains('INFO')).toBe(
true,
);
expect(
container.querySelector('.line')?.classList.contains('severity-info-0'),
).toBe(true);
});
it('renders correctly with different types', () => {
const { container: containerInfo } = render(
<LogStateIndicator type="INFO" fontSize={FontSize.MEDIUM} />,
);
expect(containerInfo.querySelector('.line')?.classList.contains('INFO')).toBe(
true,
);
const { container: containerWarning } = render(
<LogStateIndicator type="WARNING" fontSize={FontSize.MEDIUM} />,
<LogStateIndicator severityText="INFO" fontSize={FontSize.MEDIUM} />,
);
expect(
containerWarning.querySelector('.line')?.classList.contains('WARNING'),
containerInfo.querySelector('.line')?.classList.contains('severity-info-0'),
).toBe(true);
const { container: containerWarning } = render(
<LogStateIndicator severityText="WARNING" fontSize={FontSize.MEDIUM} />,
);
expect(
containerWarning
.querySelector('.line')
?.classList.contains('severity-warn-0'),
).toBe(true);
const { container: containerError } = render(
<LogStateIndicator type="ERROR" fontSize={FontSize.MEDIUM} />,
<LogStateIndicator severityText="ERROR" fontSize={FontSize.MEDIUM} />,
);
expect(
containerError.querySelector('.line')?.classList.contains('ERROR'),
containerError
.querySelector('.line')
?.classList.contains('severity-error-0'),
).toBe(true);
});
});

View File

@@ -3,6 +3,8 @@ import './LogStateIndicator.styles.scss';
import cx from 'classnames';
import { FontSize } from 'container/OptionsMenu/types';
import { getLogTypeBySeverityNumber } from './utils';
export const SEVERITY_TEXT_TYPE = {
TRACE: 'TRACE',
TRACE2: 'TRACE2',
@@ -42,18 +44,112 @@ export const LogType = {
UNKNOWN: 'UNKNOWN',
} as const;
// Severity variant mapping to CSS classes
const SEVERITY_VARIANT_CLASSES: Record<string, string> = {
// Trace variants - forest-600 to forest-200
TRACE: 'severity-trace-0',
Trace: 'severity-trace-1',
trace: 'severity-trace-2',
trc: 'severity-trace-3',
Trc: 'severity-trace-4',
// Debug variants - aqua-600 to aqua-200
DEBUG: 'severity-debug-0',
Debug: 'severity-debug-1',
debug: 'severity-debug-2',
dbg: 'severity-debug-3',
Dbg: 'severity-debug-4',
// Info variants - robin-600 to robin-200
INFO: 'severity-info-0',
Info: 'severity-info-1',
info: 'severity-info-2',
Information: 'severity-info-3',
information: 'severity-info-4',
// Warn variants - amber-600 to amber-200
WARN: 'severity-warn-0',
WARNING: 'severity-warn-0',
Warn: 'severity-warn-1',
warn: 'severity-warn-2',
warning: 'severity-warn-3',
Warning: 'severity-warn-4',
wrn: 'severity-warn-3',
Wrn: 'severity-warn-4',
// Error variants - cherry-600 to cherry-200
// eslint-disable-next-line sonarjs/no-duplicate-string
ERROR: 'severity-error-0',
Error: 'severity-error-1',
error: 'severity-error-2',
err: 'severity-error-3',
Err: 'severity-error-4',
ERR: 'severity-error-0',
fail: 'severity-error-2',
Fail: 'severity-error-3',
FAIL: 'severity-error-0',
// Fatal variants - sakura-600 to sakura-200
// eslint-disable-next-line sonarjs/no-duplicate-string
FATAL: 'severity-fatal-0',
Fatal: 'severity-fatal-1',
fatal: 'severity-fatal-2',
// eslint-disable-next-line sonarjs/no-duplicate-string
critical: 'severity-fatal-3',
Critical: 'severity-fatal-4',
CRITICAL: 'severity-fatal-0',
crit: 'severity-fatal-3',
Crit: 'severity-fatal-4',
CRIT: 'severity-fatal-0',
panic: 'severity-fatal-2',
Panic: 'severity-fatal-3',
PANIC: 'severity-fatal-0',
};
function getSeverityClass(
severityText?: string,
severityNumber?: number,
): string {
// Priority 1: Use severityText for exact variant mapping
if (severityText) {
const variantClass = SEVERITY_VARIANT_CLASSES[severityText.trim()];
if (variantClass) {
return variantClass;
}
}
// Priority 2: Use severityNumber for base color (use middle shade as default)
if (severityNumber) {
const logType = getLogTypeBySeverityNumber(severityNumber);
if (logType !== LogType.UNKNOWN) {
return `severity-${logType.toLowerCase()}-0`; // Use middle shade (index 2)
}
}
return 'severity-info-0'; // Fallback to CSS classes based on type
}
function LogStateIndicator({
type,
fontSize,
severityText,
severityNumber,
}: {
type: string;
fontSize: FontSize;
severityText?: string;
severityNumber?: number;
}): JSX.Element {
const severityClass = getSeverityClass(severityText, severityNumber);
return (
<div className="log-state-indicator">
<div className={cx('line', type, fontSize)}> </div>
<div className={cx('line', fontSize, severityClass)} />
</div>
);
}
LogStateIndicator.defaultProps = {
severityText: '',
severityNumber: 0,
};
export default LogStateIndicator;

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