Compare commits

..

174 Commits

Author SHA1 Message Date
ahrefabhi
070decb102 fix(query): added fix for multi value context and in-place replacement 2025-07-01 01:51:22 +05:30
ahrefabhi
0fbe60d68f feat: enhance IQueryPair to support multi-value operators and update query context handling 2025-06-29 17:26:43 +05:30
SagarRajput-7
7e877790d0 feat: new qb selectedfields changes for logs and traces 2025-06-27 18:36:09 +05:30
ahrefabhi
3b2d3c3849 fix: simplify condition for wrapping string values in quotes and comment out query context display 2025-06-27 18:29:28 +05:30
ahrefabhi
815d3b8bc0 feat: enhance query processing to support negation context and improve space handling 2025-06-27 18:29:28 +05:30
ahrefabhi
e529ad8d57 chore: added grammer parity for frontend grammer with main grammer 2025-06-27 18:29:28 +05:30
ahrefabhi
c8f36de6fa feat: add support for negation context in query processing 2025-06-27 18:29:28 +05:30
ahrefabhi
85caefa945 feat: enhance query context to support detection of values wrapped in quotes 2025-06-27 18:29:28 +05:30
ahrefabhi
62d8dd929a chore: removed IS_NULL and IS_NOT_NULL and added NOT_EXISTS 2025-06-27 18:29:28 +05:30
ahrefabhi
afceff33d6 feat: added IS_NULL and IS_NOT_NULL operators and fixed support for not value operators 2025-06-27 18:29:28 +05:30
ahrefabhi
ed77c6abd0 chore: updated grammer for value, added parsetree for finding current context 2025-06-27 18:29:27 +05:30
SagarRajput-7
5575334893 fix: implemented the filter retention across view switch in explorer pages 2025-06-27 18:29:27 +05:30
SagarRajput-7
137a3f6d27 fix: added metricName to the metric where clause keys api 2025-06-27 18:29:27 +05:30
SagarRajput-7
c332a3c48a fix: removed hadrcoded values suggestion for Having Filter 2025-06-27 18:29:27 +05:30
SagarRajput-7
84ff35100a fix: fixed infinite loop of states around operator 2025-06-27 18:29:27 +05:30
SagarRajput-7
67ce050f53 fix: handled qb - order, group and having's vertical expansion 2025-06-27 18:29:27 +05:30
SagarRajput-7
ceefe50d82 fix: removed noop from suggestions and default values 2025-06-27 18:29:26 +05:30
SagarRajput-7
04c9e852e6 fix: fixed metric having clause and traces order sorting 2025-06-27 18:29:26 +05:30
SagarRajput-7
97cd377fa6 feat: new query builder misc fixes (#8359)
* feat: qb fixes

* feat: fixed handlerunquery props

* feat: fixes logs list order by

* feat: fix logs order by issue

* feat: safety check and order by correction

* feat: updated version in new create dashboards

* feat: added new formatOptions for table and fixed the pie chart plotting

* feat: keyboard shortcut overriding issue and pie ch correction in dashboard views

* feat: fixed dashboard data state management across datasource * paneltypes

* feat: fixed explorer pages data management issues

* feat: integrated new backend payload/request diff, to the UI types

* feat: fixed the collapse behaviour of QB - queries

* feat: fix order by and default aggregation to count()
2025-06-27 18:29:26 +05:30
SagarRajput-7
157213defc feat: resolved conflicts 2025-06-27 18:29:25 +05:30
SagarRajput-7
8092df8961 Query builder misc - fixes (#8295)
* feat: trace and logs explorer fixes

* fix: ui fixes

* fix: handle multi arg aggregation

* feat: explorer pages fixes

* feat: added fixes for order by for datasource

* feat: metric order by issue

* feat: support for paneltype selectedview tab switch

* feat: qb v2 compatiblity with url's composite query

* feat: conversion fixes

* feat: where clause and aggregation fix

---------

Co-authored-by: Yunus M <myounis.ar@live.com>
2025-06-27 18:29:25 +05:30
Yunus M
3c895981d9 feat: fetch more keys is complete list not already fetched 2025-06-27 18:29:25 +05:30
SagarRajput-7
a058dac45b feat: query_range migration from v3/v4 -> v5 (#8192)
* feat: query_range migration from v3/v4 -> v5

* feat: cleanup files

* feat: cleanup code

* feat: metric payload improvements

* feat: metric payload improvements

* feat: data retention and qb v2 for dashboard cleanup

* feat: corrected datasource change daata updatation in qb v2

* feat: fix value panel plotting with new query v5

* feat: alert migration

* feat: fixed aggregation css

* feat: explorer pages migration

* feat: trace and logs explorer fixes
2025-06-27 18:29:25 +05:30
Yunus M
a18106f5d8 fix: responsiveness issues 2025-06-27 18:29:24 +05:30
Yunus M
ea88177936 feat: where clause key updates 2025-06-27 18:29:24 +05:30
Yunus M
84a17dd376 feat: update styles for light mode 2025-06-27 18:29:24 +05:30
Yunus M
f699773aec feat: show errors 2025-06-27 18:29:24 +05:30
Yunus M
d46d1a0f24 feat: update context and show suggestions on select 2025-06-27 18:29:24 +05:30
Yunus M
a3b66935d8 feat: add a space after selecting a value from suggestion 2025-06-27 18:29:24 +05:30
Yunus M
1f8c97cd5b feat: improve suggestion ux in query search 2025-06-27 18:29:24 +05:30
Yunus M
2659e03564 feat: ui improvements 2025-06-27 18:29:24 +05:30
Yunus M
121696c1d7 feat: handle close on blur 2025-06-27 18:29:24 +05:30
Yunus M
20be9dd600 feat: query search component clean up 2025-06-27 18:29:24 +05:30
Yunus M
45e4c65c9f feat: handle having option autocomplete ux 2025-06-27 18:29:24 +05:30
Yunus M
b7490fcf68 feat: disable clicking on placeholder items in suggestions 2025-06-27 18:29:24 +05:30
Yunus M
4bfd4e536c feat: improve having suggestions 2025-06-27 18:29:24 +05:30
Yunus M
f7d5a26403 feat: handle add ons 2025-06-27 18:29:24 +05:30
Yunus M
f87594243e feat: handle list panel type options 2025-06-27 18:29:24 +05:30
Yunus M
dacc3d6d9e feat: pass index to query addons 2025-06-27 18:29:24 +05:30
Yunus M
6b28ec2f7f feat: update qb elements based on panel type 2025-06-27 18:29:24 +05:30
Yunus M
9b757af028 feat: hide extra qb elements 2025-06-27 18:29:24 +05:30
Yunus M
ce87bcae71 feat: use qb-v2 in explorers and alerts 2025-06-27 18:29:24 +05:30
Yunus M
25fb8b6561 feat: update explorer views 2025-06-27 18:29:24 +05:30
Yunus M
428a16326a feat: update logs, metrics and traces qb 2025-06-27 18:29:24 +05:30
Yunus M
78fec2188d feat: query builder layout updates 2025-06-27 18:29:24 +05:30
Yunus M
c5650cc131 fix: minor fixes 2025-06-27 18:29:24 +05:30
Yunus M
f67213096c feat: create separate containers for traces, logs and metrics qbs 2025-06-27 18:29:24 +05:30
Yunus M
d71f85a8ec feat: metrics qb 2025-06-27 18:29:24 +05:30
Yunus M
9335261314 fix: update dropdown css 2025-06-27 18:29:24 +05:30
Yunus M
0f5c54cabb feat: remove () from suggestions 2025-06-27 18:29:24 +05:30
Yunus M
0204337396 feat: handle parenthesis and conjunction operators 2025-06-27 18:29:24 +05:30
Yunus M
8101fef874 feat: support multiple having key value pairs 2025-06-27 18:29:24 +05:30
Yunus M
2d223fe9e8 feat: move state to context 2025-06-27 18:29:24 +05:30
Yunus M
de464e6042 feat: handle having options creation 2025-06-27 18:29:24 +05:30
Yunus M
ea42e4db6b feat: hide already used variables 2025-06-27 18:29:24 +05:30
Yunus M
2b5d2f0061 fix: show operator suggestions only on manual trigger or valid key 2025-06-27 18:29:24 +05:30
Yunus M
a013cc0fd3 fix: handle autocomplete 2025-06-27 18:29:24 +05:30
Yunus M
e68d860adf fix: update styles 2025-06-27 18:29:23 +05:30
Yunus M
9ed93ae5ac fix: update css 2025-06-27 18:29:23 +05:30
Yunus M
9989af10d6 feat: handle multie select functions 2025-06-27 18:29:23 +05:30
Yunus M
1bc89c9d1a feat: handle field suggestions for aggregate operators 2025-06-27 18:29:23 +05:30
Yunus M
3fbe111bc0 feat: support aggregation function with values 2025-06-27 18:29:23 +05:30
Yunus M
c449d1da8e feat: add groupBy, having, order by, limit and legend format 2025-06-27 18:29:23 +05:30
Yunus M
4635da0ee8 feat: handle multie select values better 2025-06-27 18:29:23 +05:30
Yunus M
67453e27f7 feat: improve suggestions 2025-06-27 18:29:23 +05:30
Yunus M
fdcc6a6c92 feat: console log context based on cursor position 2025-06-27 18:29:23 +05:30
Yunus M
62c71e6306 fix: handle . notation keywords better 2025-06-27 18:29:23 +05:30
Yunus M
e2e535eaca feat: remove card container above where clause 2025-06-27 18:29:23 +05:30
Yunus M
2520718afb feat: use new qb in logs explorer 2025-06-27 18:29:23 +05:30
Yunus M
0ffa666903 feat: handle parenthesis 2025-06-27 18:29:23 +05:30
Yunus M
c653e83461 feat: handle value selection 2025-06-27 18:29:23 +05:30
Yunus M
b80cf96faf feat: styling updates 2025-06-27 18:29:23 +05:30
Yunus M
a2126ad22c feat: handle string and number values correctly 2025-06-27 18:29:23 +05:30
Yunus M
5a75df30e2 feat: handle async value fetching 2025-06-27 18:29:23 +05:30
Yunus M
aeca98b6aa feat: update the context with additonal properties 2025-06-27 18:29:23 +05:30
Yunus M
53b31ae516 feat: styling updates 2025-06-27 18:29:23 +05:30
Yunus M
209828de01 feat: update theme and syntax highlighting 2025-06-27 18:29:23 +05:30
Yunus M
491a0140e3 feat: handle context switch 2025-06-27 18:29:23 +05:30
Yunus M
b9494a3375 feat: handle multiple spaces 2025-06-27 18:29:23 +05:30
Yunus M
d4b379ccc0 feat: integrate the apis 2025-06-27 18:29:23 +05:30
Yunus M
a7ff27d30c feat: update context logic and return auto-suggestions based on context 2025-06-27 18:29:23 +05:30
Yunus M
6008e8df72 feat: add apis and hooks 2025-06-27 18:29:23 +05:30
Yunus M
27d5e16d18 feat: update context to recognise conjunction operator 2025-06-27 18:29:23 +05:30
Yunus M
24d6b48ad4 feat: add codemirror 2025-06-27 18:29:23 +05:30
Yunus M
78af24b4df feat: add types, base components 2025-06-27 18:29:23 +05:30
Yunus M
45fcf746b0 feat: add antlr4, parser files and grammar 2025-06-27 18:29:23 +05:30
Piyush Singariya
d174038dce fix: panic after connecting to collector (#8344) 2025-06-26 16:34:49 +05:30
Srikanth Chekuri
78d09e2940 chore: log the request and expected response payload (#8341) 2025-06-26 09:40:31 +00:00
Srikanth Chekuri
6cb7f152e1 chore: bump opamp-go version (#8310) 2025-06-26 15:01:17 +05:30
Amlan Kumar Nandy
f6730d3d09 chore: update memory usage field in hosts list to exclude cached memory (#8173) 2025-06-26 13:36:16 +07:00
Nityananda Gohain
899a6ab70a fix: fetch only required traces fields (#8351)
* fix: fetch only required traces fields

* fix: remove only logs case in field name

* fix: add extra if condition for logs json field names

* fix: tests
2025-06-25 18:29:22 +05:30
Amlan Kumar Nandy
a4b852bb99 chore: fix environment filter in infra monitoring (#8357) 2025-06-25 10:46:05 +00:00
Vishal Sharma
92cd108c0d doc: update docker metrics doc link (#8358) 2025-06-25 15:55:12 +05:30
SagarRajput-7
34c116fc7e fix: fixed stepInterval not getting updated in the request payload for Bar (#8350)
* fix: fixed stepInterval not getting updated in the request payload for Bar

* fix: added test case
2025-06-25 11:47:59 +05:30
Vibhu Pandey
250646a354 feat(telemetry): remove telemetry (#8326) 2025-06-24 15:59:23 +00:00
Shaheer Kochai
00191d5774 fix: show status message in trace details v2 drawer (#8346) 2025-06-24 15:36:36 +00:00
Sahil Khan
525a0d7a1a fix: back button issue in trace details page (#8347) 2025-06-24 15:27:24 +00:00
Sahil Khan
564edc7430 fix: added network call on search in explorercolumnsrenderer with debounce (#8325) 2025-06-24 15:16:53 +00:00
Amlan Kumar Nandy
78f396b94a chore: add environment filter in infra monitoring (#8309) 2025-06-24 13:25:34 +00:00
Sahil Khan
9e53c150b8 fix: added missing context provider (#8342) 2025-06-24 18:00:47 +05:30
Shaheer Kochai
f80a6c3014 feat: add support for expandable popover for stack message and body in trace details page (#8330)
* feat: add support for expandable popover for stack message and body in trace details page

* refactor: overall improvements + refactor ExpandableAttribute
2025-06-24 11:52:33 +00:00
Vishal Sharma
1eff6d82c9 fix: color code expiry dates in ingestion key settings (#8323) 2025-06-24 13:00:30 +05:30
Shaheer Kochai
f138eff26c fix: fix the flickering in logs explorer table view (#8304) 2025-06-24 08:31:13 +04:30
Vishal Sharma
50f3fc0ff9 Chore/update request texts and integrations (#8305)
* chore: update request dashboard and integrations text

* chore: hide tab/table when data is not available

* chore: add new template text

* fix: test case
2025-06-23 14:24:47 +00:00
Yunus M
ebcb172614 feat(user-profile): update organisation onboarding questions (#8206)
* feat: update org onboarding questions

* feat: type updates

* chore(user-profile): update the onboarding questions

---------

Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
2025-06-23 13:27:05 +00:00
Shaheer Kochai
133c0deaa8 fix: prevent sending order by id with traces query (#8250)
* fix: prevent sending order by id with traces query

* test: write tests for preventing sending order by id with traces query
2025-06-23 12:34:59 +00:00
Shaheer Kochai
35e8165463 fix: recalculate the query_range start and end timestamps for logs explorer chart and list queries (#8277)
* fix: recalculate start and end time only for relative time ranges

* fix: don't dsipatch UpdateTimeInterval on initial load

* fix: change list query to state instead of memo

* fix: fix the failing test

* chore: improvement to the test

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
2025-06-23 09:06:46 +00:00
Srikanth Chekuri
6d009c6607 chore: recognize variable in expression (#8328) 2025-06-23 08:30:50 +00:00
Amlan Kumar Nandy
f0994e52c0 chore: alerts fixes and improvements (#8327) 2025-06-23 07:08:17 +00:00
Srikanth Chekuri
7f5b388722 chore: add time range optimization for trace id search (#8317) 2025-06-23 04:09:19 +00:00
Sahil Khan
b11a4c0c21 fix: log details filters use data types from log data response as primary data type (#8278)
* fix: log details filters use data types from log data response as primary data type

* chore: added test cases

* test: add comprehensive unit tests for chooseAutocompleteFromCustomValue function

* fix: added datatypes to util and test cases

* fix: added new tests
2025-06-22 10:58:43 +00:00
Srikanth Chekuri
bbb21f608f chore: more validation, zero values and enfore max step interval (#8319) 2025-06-21 17:49:33 +05:30
Nityananda Gohain
50a5b88708 fix: fetch only required log fields (#8299)
* fix: fetch only required log fields

* fix: update old endpoints

* fix: remove old code
2025-06-21 04:37:57 +00:00
Vibhu Pandey
5601c0886d chore(signoz): deprecate all flags (#8308)
Deprecate all flags

- Use querier.config.fluxInterval in lieu of passing `--flux-interval` and `--flux-interval-for-trace-detail`
- Remove `--gateway-url`
- Use telemetrystore.clickhouse.cluster in lieu of passing `--cluster` or `--cluster-name`
- Add an `unparam` check in the linter. Updated some functions across the querier codebase to be compatible with this linter.
- Remove prometheus config from docker builds.
2025-06-21 00:55:38 +05:30
Srikanth Chekuri
5b342b9b5d chore: handle nan/inf in response (#8318) 2025-06-20 22:26:25 +05:30
Vishal Sharma
7ec59c3c77 chore: move posthog and appcues ids to userId and orgId (#8316) 2025-06-20 13:21:59 +00:00
Nityananda Gohain
a12990f0bd fix: update trace panel query (#8315) 2025-06-20 11:18:17 +00:00
Yunus M
1ee1ca7951 fix: update app layout height based on banners visible (#8307)
* fix: update app layout height based on banners visible

* fix: show banners only in logged in state

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
2025-06-20 11:08:30 +05:30
Abhi kumar
3b1bf34d3e feat(changelog): show changelogs for newer versions available (#8270)
* feat(changelog): add getChangelogByVersion API and related types

* feat(changelog): implement ChangelogModal and ChangelogRenderer components with styles

* test(dateUtils): add unit tests for formatDate utility

* chore(changelog): fixed pr review changes

* style(ChangelogRenderer): format SCSS for improved readability

* feat(SideNav): integrate ChangelogModal and manage its visibility state

* feat(changelog): refactor changelog handling and integrate into app state

* test(ChangelogModal): add unit tests for scroll functionality and data rendering

* test(ChangelogRenderer): add unit tests for rendering changelog details

* test(ChangelogModal, ChangelogRenderer): refactor tests

* fix(applayout): bot fetching changelog for cloud users

* fix(ChangelogModal): update footer to display feature count dynamically

* fix(ChangelogModal): update link for workspace migration to point to releases page

* feat(ChangelogModal): enhance footer layout and update link behavior

* test(ChangelogModal): update link for workspace migration to point to releases page

* refactor(AppContext): migrate changelog state management to context and update related components

* feat(test-utils): add changelog state and updateChangelog mock to app context

* test(changelogModal): fixed test by adding mock for useAppContext

* fix: added PR review fixes

* Fixed css variable name in ChangelogModal.styles.scss

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

* fix(style): added light mode support for changelog modal

* Fixed heading color token

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

* fix: remove debug log for isLatestVersion in AppLayout

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-06-20 10:55:52 +05:30
Vibhu Pandey
fbcff29fae chore(sqlstore): remove sqlx (#8306)
## 📄 Summary

remove sqlx
2025-06-20 00:34:54 +05:30
Yunus M
81fcca3bd3 fix: use pathname to get channel id while saving (#8303) 2025-06-19 14:57:32 +00:00
Yunus M
4f7d84aa37 fix: use pathname to get channel id (#8298) 2025-06-19 19:28:47 +05:30
Abhi kumar
8f8dedb8b3 fix(sidebar): added fix routes not highlighting, minor gitter fix (#8297)
* fix(sidebar): added fix routes not highlighting, minor gitter fix

* chore(routes): tsc fix

* fix(private): added check in private route for routes with no role

* fix(private): minor fix in condition

* chore: added roles in empty routes
2025-06-19 16:17:54 +05:30
Ankit Nayan
3f65229506 fix: tracefunnel analytics duration fixes + 2-step funnel fixes (#8294) 2025-06-19 06:19:31 +00:00
Srikanth Chekuri
f006260719 chore: find contradictory condition keys in expression (#8238) 2025-06-19 05:40:50 +00:00
Piyush Singariya
3fc8f6c353 fix: JSON Query parse string int value (#8292)
* fix: json query parse string int

* chore: minor

* Update pkg/query-service/app/logs/v3/enrich_query_test.go

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

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
2025-06-18 16:14:23 +00:00
Yunus M
e02ae9a5c4 feat: show billing , settings to admin when workspace is blocked (#8291)
* feat: show billing , settings to admin when workspace is blocked

* feat: enable keyboard shortcuts page for all

* feat: remove duplicated option
2025-06-18 20:43:30 +05:30
Nityananda Gohain
1989d07e52 fix: delete existing agents in migration (#8289) 2025-06-18 18:06:36 +05:30
Shaheer Kochai
78194ae955 chore: remove dev env check (#7994)
Co-authored-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
2025-06-18 07:45:54 +00:00
Shivanshu Raj Shrivastava
da1b6d1ed0 feat: adds a final part of trace funnel feature (analytics APIs, and analytics queries) implementation (#8129)
* feat: trace funnel queries

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* fix: update access

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* fix: fix queries

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* fix: minor fix in handler

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* fix: update clauses

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* fix: update step overview queries

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* fix: add new api endpoints for analytics (#8253)

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>

* fixing steps and funnel (#8283)

* add todo: remove identical function

---------

Signed-off-by: Shivanshu Raj Shrivastava <shivanshu1333@gmail.com>
Co-authored-by: Ankit Nayan <ankit@signoz.io>
2025-06-18 07:40:20 +00:00
Amlan Kumar Nandy
d3c76ae8be chore: update alert details error state (#8246) 2025-06-18 07:20:07 +00:00
Shaheer Kochai
bed3dbc698 chore: funnel run and save flow changes (#8231)
* feat: while the funnel steps are invalid, handle auto save in local storage

* chore: handle lightmode style in 'add span to funnel' modal

* fix: don't save incomplete steps state in local storage if last saved configuration has valid steps

* chore: close the 'Add span to funnel' modal on clicking save or discard

* chore: deprecate the run funnel flow for unexecuted funnel

* feat: change the funnel configuration save logic, and deprecate auto save

* refactor: send all steps in the payload of analytics/overview

* refactor: send all steps in the payload of analytics/steps (graph API)

* chore: send all steps in the payload of analytics/steps/overview API

* chore: send funnel steps with slow and error traces + deprecate the refetch on latency type change

* chore: overall improvements

* chore: change the save funnel icon + increase the width of funnel steps

* fix: make the changes w.r.t. the updated funnel steps validation API + bugfixes

* fix: remove funnelId from funnel results APIs

* fix: handle edge case i.e. refetch funnel results on deleting a funnel step

* chore: remove funnel steps configuration cache on removing funnel

* chore: don't refetch the results on changing the latency type

* fix: fix the edge cases of save funnel button being enabled even after saving the funnel steps

* chore: remove the span count column from top traces tables

* fix: fix the failing CI check by removing unnecessary props / fixing the types
2025-06-18 06:08:41 +00:00
Amlan Kumar Nandy
66affb0ece chore: add unit tests for hosts list in infra monitoring (#8230) 2025-06-18 05:53:42 +00:00
Vibhu Pandey
75f62372ae feat(analytics): move frontend event to group_id (#8279)
* chore(api_key): add api key analytics

* feat(analytics): move frontend events

* feat(analytics): add collect config

* feat(analytics): add collect config

* feat(analytics): fix traits

* feat(analytics): fix traits

* feat(analytics): fix traits

* feat(analytics): fix traits

* feat(analytics): fix traits

* feat(analytics): fix factor api key

* fix(analytics): fix org stats

* fix(analytics): fix org stats
2025-06-18 01:54:55 +05:30
Sahil Khan
a3ac307b4e fix: sentry issues SIGNOZ-UI-Q9 SIGNOZ-UI-QA (#8281) 2025-06-17 23:44:21 +05:30
Vikrant Gupta
7672d2f636 chore(user): return user resource on register user request (#8271) 2025-06-17 17:26:06 +05:30
aniketio-ctrl
e3018d9529 fix(8232): added fix for error graph in services tab (#8263) 2025-06-17 08:08:38 +00:00
Nityananda Gohain
385ee268e3 fix: use first org in agent migration (#8269)
* fix: exit gracefull if there are more than one org

* fix: use first org
2025-06-17 06:25:12 +00:00
Piyush Singariya
01036a8a2f fix: top level keys EXIST and NOTEXIST filter simulation (#8255)
* fix: top level keys EXIST and NOTEXIST filter simulation

* test: fix tests

* test: temporarily change collector version

* test: updating go.mod

* fix: tests

* chore: revert changes

* chore: update collector's reference to stable version
2025-06-17 11:28:40 +05:30
Srikanth Chekuri
1542b9d6e9 chore: disallow unknown fields and address gaps (#8237) 2025-06-16 23:11:28 +05:30
Nityananda Gohain
8455349459 fix: support orgId and postgres in agents (#7327)
* fix: initial commit for agents

* fix: remove frontend package manger commit

* fix: use sqlstore

* fix: opamp server changes

* fix: tests

* fix: tests

* fix: minor changes

* fix: migrations

* fix: use uuid7

* fix: use default orgID for single tenant

* fix: pipelines tests fixed

* fix: use correct agentId

* fix: use orgID in coordinator

* fix: fix tests

* fix: remove redundant hash check

* fix: migration

* fix: migration

* fix: address comments

* fix: rename migration file

* fix: remove unwanted orgid code

* fix: use orggetter

* fix: comment

* fix: schema cleanup

* fix: minor changes

* chore: addresses changes

* fix: add back agentID as it used ulid

* fix: keep only 50 agents for an orgId

* chore: explicitly specify text type

* chore: use valuer.uuid for orgid

* fix: linting complain

* chore: final fixes

* chore: minor changes

* fix: add not null

* fix: fe tests

---------

Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
2025-06-16 20:07:16 +05:30
aniketio-ctrl
c488a24d09 fix(prom-aggr): fix prom aggregation queries using utf-8 charset (#8262)
* fix(prom-aggr): added fix for prom aggregation

* fix(prom-aggr): added fix for prom aggregation
2025-06-16 19:42:17 +05:30
Vikrant Gupta
9091cf61fd chore(github): fix codeowners file (#8261) 2025-06-16 11:37:07 +00:00
Ekansh Gupta
eeb2ab3212 feat: added support for trace_operators in query range v5 (#8165)
* feat: added support for trace_operators in query range v5

* feat: added support for trace_operators in query range v5

* feat: added support for trace_operators in query range v5

* feat: added support for trace_operators in query range v5\n

* feat: added support for trace_operators in query range v5\n

* feat: added support for trace_operators in query range v5

* feat: added support for trace_operators in query range v5

* feat: added support for trace_operators in query range v5

* feat: added support for trace_operators in query range v5

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-06-16 16:43:51 +05:30
Nageshbansal
3f128f0f1d fix: configs in multi-node docker-swarm cluster (#8239) 2025-06-16 11:42:02 +05:30
Vibhu Pandey
59ff7ed1e1 feat(licensing): add analytics (#8252) 2025-06-16 01:09:41 +05:30
Vibhu Pandey
d236b6ce1e feat(statsreporter): add stats for telemetry.*.last_observed.time (#8251)
## 📄 Summary

- add stats for telemetry.*.last_observed.time
2025-06-16 00:02:17 +05:30
Dimitris Mavrommatis
44b118a212 fix: span links tab to span details drawer (#7888)
* fix: span links tab to span details drawer

* Update LinkedSpans.styles.scss

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
2025-06-15 16:22:36 +04:30
Dimitris Mavrommatis
3fc6f7ee63 feat(trace): add visuals for events on span waterfall and flamegraph (#7889)
* fix: add visuals on waterfall and flamegraph for span events

* fix: correct offsets for events

* fix: addressed comments

* chore: update the name of the import

* fix: interface change

* chore: formatting

---------

Co-authored-by: Nityananda Gohain <nityanandagohain@gmail.com>
Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
2025-06-15 10:39:39 +00:00
Sahil Khan
f1016baf03 chore: updated http-proxy-middleware to 3.0.5 from 3.0.3 (#8245) 2025-06-13 21:35:48 +05:30
Amlan Kumar Nandy
e5c0d9e44a chore: allow url as label value in alerts (#8244) 2025-06-13 17:47:01 +07:00
Yunus M
e51056c804 fix: use preference.name rather than preference.key (#8234) 2025-06-13 11:44:42 +05:30
Nityananda Gohain
7d8dad4550 Revert "fix: remove whitespace from sso cert (#8141)" (#8233)
This reverts commit 44ea237039.
2025-06-12 22:39:24 +05:30
Yunus M
c477e0ef16 feat: sidebar revamp (#8087)
* feat: sidebar revamp - initial commit

* feat: move billinga and other isolated routes to settings

* feat: handle channel related routes

* feat: update account settings page

* feat: show dropdown for secondary items

* feat: handle reordering of pinned nav items

* feat: improve font load performance

* feat: update font reference

* feat: update page content styles

* feat: handle external links in sidebar

* feat: handle secondary nav item clicks

* feat: handle pinned nav items reordering

* feat: handle sidenav pinned state using preference, handle light mode

* feat: show sidenav items conditionally

* feat: show version diff indicator only to self hosted users

* feat: show billing to admins only and integrations to cloud and enterprise users

* feat: update fallback link

* feat: handle settings menu items

* fix: settings page reload on nav chnage

* feat: intercom to pylon

* feat: show invite user to admin only

* feat: handle review comments

* chore: remove react query dev tools

* feat: minor ui updates

* feat: update changes based on preference store changes

* feat: handle sidenav shortcut state

* feat: handle scroll for more

* feat: maintain shortcuts order

* feat: manage license ui updates

* feat: manage settings options based on license and roles

* feat: update types

* chore: add logEvents

* feat: update types

* chore: fix type errors

* chore: remove unused variable

* feat: update my settings page test cases

---------

Co-authored-by: makeavish <makeavish786@gmail.com>
2025-06-12 19:55:32 +05:30
Shaheer Kochai
fff7f8fc76 feat: add span scope filter to trace details page (#8005)
* feat: add span scope filter to trace details page

* chore: add tests for the span scope selector flows when onchange and query are provided

* refactor: remove the unnecessary queryName prop and infer it from query

* fix: fix the failing span scope selector tests
2025-06-12 11:03:28 +00:00
Shaheer Kochai
8cfeef4521 fix: fix sentries (#8003)
* fix: handle potential undefined values in groupBy calculation in TracesExplorer

* fix: add optional chaining for aggregateAttribute key check in Query component

* fix: add optional chaining for filters in SpanScopeSelector to handle potential undefined values

* fix: fix the warning in logs chart by adding the missing date-time format option

* fix: improve trace graph allDataPoints null check

* chore: remove the keys.length from null check
2025-06-12 15:28:06 +04:30
Sahil Khan
d85a1a21ac feat: generalised preferences framework (#7903)
* feat: preferences framework generalised scaffolded

* feat: preferences framework integrated logs & saved logs views

* feat: fixed bugs in saved views for traces

* fix: removed unused file

* fix: wrapped metric explorer inside preferences context as it uses useoptions hook

* feat: added tests for preferences framework alongside some minor bugs improvements

* chore: added tests for traces loader and updater

* chore: fixed failing tests due to new context for preferences

* fix: minor saved views handling bug

* fix: minor pref fix

* fix: breaking tests

* fix: undo removal of pref context from live logs

* feat: split the logic of columns and formatting load in logs

* fix: breaking tests

* fix: pr comments

* fix: minor bug and pr comments regarding better resync

* fix: bugs in internal flows

* fix: url pref sync

* fix: minor bug fix

* fix: fixed failing tests

* fix: fixed failing tests
2025-06-11 10:06:01 +00:00
primus-bot[bot]
17f48d656d chore(release): bump to v0.87.0 (#8222)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2025-06-11 12:06:17 +05:30
Srikanth Chekuri
2d6774da68 fix: add missing denominator for reset case (#8180) 2025-06-11 11:32:50 +05:30
Vibhu Pandey
62a9d7e602 docs(contributing): add endpoint docs (#8215)
* docs(contributing): add endpoint docs

* docs(contributing): add endpoint docs

* docs(contributing): add endpoint docs

* docs(contributing): add endpoint docs

* docs(contributing): add endpoint docs
2025-06-10 17:25:07 +00:00
Vikrant Gupta
3a2c7a7a68 fix(dashboard): create dashboard panic for id (#8214) 2025-06-10 21:31:56 +05:30
Sahil Khan
33e70d1f37 fix: traces back button issue (#8041)
Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
2025-06-10 13:25:59 +00:00
Srikanth Chekuri
85f04e4bae chore: add querier HTTP API endpoint and bucket cache implementation (#8178)
* chore: update types
1. add partial bool to indicate if the value covers the partial interval
2. add optional unit if present (ex: duration_nano, metrics with units)
3. use pointers wherever necessary
4. add format options for request and remove redundant name in query envelope

* chore: fix some gaps
1. make the range as [start, end)
2. provide the logs statement builder with the body column
3. skip the body filter on resource filter statement builder
4. remove unnecessary agg expr rewriter in metrics
5. add ability to skip full text in where clause visitor

* chore: add API endpoint for new query range

* chore: add bucket cache implementation

* chore: add fingerprinting impl and add bucket cache to querier

* chore: add provider factory
2025-06-10 12:56:28 +00:00
Shaheer Kochai
53f9e7d811 chore: trace funnels bugfixes/improvements (#8114)
* fix: refetch funnel steps overview on clicking refresh

* chore: temporarily hide latency pointer from funnel steps

* chore: remove the existing filters of a step on clicking replace button

* fix(useLocalStorage): stabilize initialValue handling to prevent unnecessary re-renders

* chore: remove p99_latency references from funnel metrics

* fix(useFunnelMetrics): ensure latency type defaults to P99 when undefined
2025-06-10 12:45:25 +00:00
Vibhu Pandey
ad46e22561 docs(contributing): add provider docs (#8193) 2025-06-10 05:39:14 +00:00
Yunus M
e79195ccf1 fix: handle alert list updates (#8109) 2025-06-10 10:56:45 +05:30
Amlan Kumar Nandy
f77bb888a8 chore: add analytics for metrics explorer (#8108) 2025-06-10 04:42:49 +00:00
Amlan Kumar Nandy
baa15baea9 chore: metrics explorer summary view fixes (#8126) 2025-06-10 04:18:12 +00:00
Vibhu Pandey
316e6821f1 feat(statsreporter): report stats on stop (#8187) 2025-06-10 07:55:32 +05:30
Vibhu Pandey
a1fa2769e4 feat(statsreporter): build a statsreporter service (#8177)
- build a new statsreporter service
2025-06-09 16:43:29 +05:30
Vikrant Gupta
decb660992 chore(sqlmigration): drop the rule history and data migrations table (#8181) 2025-06-09 15:46:22 +05:30
Amlan Kumar Nandy
0acbcf8322 chore: remove critters-webpack-plugin (#8172) 2025-06-07 16:21:06 +07:00
dependabot[bot]
11eabdc2ac chore(deps): bump webpack-dev-server from 4.15.2 to 5.2.1 in /frontend (#8160)
* chore(deps): bump webpack-dev-server from 4.15.2 to 5.2.1 in /frontend

Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 4.15.2 to 5.2.1.
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v4.15.2...v5.2.1)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  dependency-version: 5.2.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

* feat: upgraded webpack-cli for compatibility fix for webpack-dev-server upgrade

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: SagarRajput-7 <162284829+SagarRajput-7@users.noreply.github.com>
Co-authored-by: SagarRajput-7 <sagar@signoz.io>
2025-06-07 07:24:18 +00:00
Vibhu Pandey
eb94554f5a feat(preference): add support for objects and arrays (#8142)
* refactor(preference): better readability

* refactor: better readability

* refactor: better readability

* fix: change frontend contract

* refactor: change frontend

* refactor: change frontend

* refactor: change frontend

* refactor: change frontend

* chore: fix tsc

* chore: fix tsc

* chore: fix tsc

* chore: fix tsc
2025-06-06 22:38:28 +05:30
618 changed files with 37724 additions and 13071 deletions

7
.github/CODEOWNERS vendored
View File

@@ -12,4 +12,9 @@
/pkg/factory/ @grandwizard28
/pkg/types/ @grandwizard28
.golangci.yml @grandwizard28
**/(zeus|licensing|sqlmigration)/ @vikrantgupta25
/pkg/zeus/ @vikrantgupta25
/pkg/licensing/ @vikrantgupta25
/pkg/sqlmigration/ @vikrantgupta25
/ee/zeus/ @vikrantgupta25
/ee/licensing/ @vikrantgupta25
/ee/sqlmigration/ @vikrantgupta25

View File

@@ -74,7 +74,8 @@ jobs:
-X github.com/SigNoz/signoz/pkg/version.variant=community
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
-X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }}
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}'
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}
-X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr'
GO_CGO_ENABLED: 1
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
DOCKER_DOCKERFILE_PATH: ./pkg/query-service/Dockerfile.multi-arch

View File

@@ -108,7 +108,8 @@ jobs:
-X github.com/SigNoz/signoz/ee/zeus.url=https://api.signoz.cloud
-X github.com/SigNoz/signoz/ee/zeus.deprecatedURL=https://license.signoz.io
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1'
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1
-X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr'
GO_CGO_ENABLED: 1
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch

View File

@@ -107,7 +107,8 @@ jobs:
-X github.com/SigNoz/signoz/ee/zeus.url=https://api.staging.signoz.cloud
-X github.com/SigNoz/signoz/ee/zeus.deprecatedURL=https://license.staging.signoz.cloud
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.staging.signoz.cloud
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.staging.signoz.cloud/api/v1'
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.staging.signoz.cloud/api/v1
-X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr'
GO_CGO_ENABLED: 1
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch

View File

@@ -7,6 +7,7 @@ linters:
- sloglint
- depguard
- iface
- unparam
linters-settings:
sloglint:

View File

@@ -90,6 +90,15 @@ apiserver:
- /api/v1/version
- /
##################### Querier #####################
querier:
# The TTL for cached query results.
cache_ttl: 168h
# The interval for recent data that should not be cached.
flux_interval: 5m
# The maximum number of concurrent queries for missing ranges.
max_concurrent_queries: 4
##################### TelemetryStore #####################
telemetrystore:
# Maximum number of idle connections in the connection pool.
@@ -103,13 +112,15 @@ telemetrystore:
clickhouse:
# The DSN to use for clickhouse.
dsn: tcp://localhost:9000
# The cluster name to use for clickhouse.
cluster: cluster
# The query settings for clickhouse.
settings:
max_execution_time: 0
max_execution_time_leaf: 0
timeout_before_checking_execution_speed: 0
max_bytes_to_read: 0
max_result_rows_for_ch_query: 0
max_result_rows: 0
##################### Prometheus #####################
prometheus:
@@ -165,12 +176,6 @@ alertmanager:
# Retention of the notification logs.
retention: 120h
##################### Analytics #####################
analytics:
# Whether to enable analytics.
enabled: false
##################### Emailing #####################
emailing:
# Whether to enable emailing.
@@ -215,3 +220,27 @@ sharder:
single:
# The org id to which this instance belongs to.
org_id: org_id
##################### Analytics #####################
analytics:
# Whether to enable analytics.
enabled: false
segment:
# The key to use for segment.
key: ""
##################### StatsReporter #####################
statsreporter:
# Whether to enable stats reporter. This is used to provide valuable insights to the SigNoz team. It does not collect any sensitive/PII data.
enabled: true
# The interval at which the stats are collected.
interval: 6h
collect:
# Whether to collect identities and traits (emails).
identities: true
##################### Gateway (License only) #####################
gateway:
# The URL of the gateway's api.
url: http://localhost:8080

View File

@@ -174,7 +174,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.86.2
image: signoz/signoz:v0.87.0
command:
- --config=/root/config/prometheus.yml
ports:

View File

@@ -100,26 +100,33 @@ services:
# - "9000:9000"
# - "8123:8123"
# - "9181:9181"
configs:
- source: clickhouse-config
target: /etc/clickhouse-server/config.xml
- source: clickhouse-users
target: /etc/clickhouse-server/users.xml
- source: clickhouse-custom-function
target: /etc/clickhouse-server/custom-function.xml
- source: clickhouse-cluster
target: /etc/clickhouse-server/config.d/cluster.xml
volumes:
- ../common/clickhouse/config.xml:/etc/clickhouse-server/config.xml
- ../common/clickhouse/users.xml:/etc/clickhouse-server/users.xml
- ../common/clickhouse/custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ../common/clickhouse/user_scripts:/var/lib/clickhouse/user_scripts/
- ../common/clickhouse/cluster.xml:/etc/clickhouse-server/config.d/cluster.xml
- clickhouse:/var/lib/clickhouse/
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:v0.86.2
image: signoz/signoz:v0.87.0
command:
- --config=/root/config/prometheus.yml
ports:
- "8080:8080" # signoz port
# - "6060:6060" # pprof port
volumes:
- ../common/signoz/prometheus.yml:/root/config/prometheus.yml
- ../common/dashboards:/root/config/dashboards
- sqlite:/var/lib/signoz/
configs:
- source: signoz-prometheus-config
target: /root/config/prometheus.yml
environment:
- SIGNOZ_ALERTMANAGER_PROVIDER=signoz
- SIGNOZ_TELEMETRYSTORE_CLICKHOUSE_DSN=tcp://clickhouse:9000
@@ -147,9 +154,11 @@ services:
- --manager-config=/etc/manager-config.yaml
- --copy-path=/var/tmp/collector-config.yaml
- --feature-gates=-pkg.translator.prometheus.NormalizeName
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ../common/signoz/otel-collector-opamp-config.yaml:/etc/manager-config.yaml
configs:
- source: otel-collector-config
target: /etc/otel-collector-config.yaml
- source: otel-manager-config
target: /etc/manager-config.yaml
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}}
- LOW_CARDINAL_EXCEPTION_GROUPING=false
@@ -186,3 +195,26 @@ volumes:
name: signoz-sqlite
zookeeper-1:
name: signoz-zookeeper-1
configs:
clickhouse-config:
file: ../common/clickhouse/config.xml
clickhouse-users:
file: ../common/clickhouse/users.xml
clickhouse-custom-function:
file: ../common/clickhouse/custom-function.xml
clickhouse-cluster:
file: ../common/clickhouse/cluster.xml
signoz-prometheus-config:
file: ../common/signoz/prometheus.yml
# If you have multiple dashboard files, you can list them individually:
# dashboard-foo:
# file: ../common/dashboards/foo.json
# dashboard-bar:
# file: ../common/dashboards/bar.json
otel-collector-config:
file: ./otel-collector-config.yaml
otel-manager-config:
file: ../common/signoz/otel-collector-opamp-config.yaml

View File

@@ -177,7 +177,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.86.2}
image: signoz/signoz:${VERSION:-v0.87.0}
container_name: signoz
command:
- --config=/root/config/prometheus.yml

View File

@@ -110,7 +110,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz:
!!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.86.2}
image: signoz/signoz:${VERSION:-v0.87.0}
container_name: signoz
command:
- --config=/root/config/prometheus.yml

View File

@@ -0,0 +1,51 @@
# Endpoint
This guide outlines the recommended approach for designing endpoints, with a focus on entity relationships, RESTful structure, and examples from the codebase.
## How do we design an endpoint?
### Understand the core entities and their relationships
Start with understanding the core entities and their relationships. For example:
- **Organization**: an organization can have multiple users
### Structure Endpoints RESTfully
Endpoints should reflect the resource hierarchy and follow RESTful conventions. Use clear, **pluralized resource names** and versioning. For example:
- `POST /v1/organizations` — Create an organization
- `GET /v1/organizations/:id` — Get an organization by id
- `DELETE /v1/organizations/:id` — Delete an organization by id
- `PUT /v1/organizations/:id` — Update an organization by id
- `GET /v1/organizations/:id/users` — Get all users in an organization
- `GET /v1/organizations/me/users` — Get all users in my organization
Think in terms of resource navigation in a file system. For example, to find your organization, you would navigate to the root of the file system and then to the `organizations` directory. To find a user in an organization, you would navigate to the `organizations` directory and then to the `id` directory.
```bash
v1/
├── organizations/
│ └── 123/
│ └── users/
```
`me` endpoints are special. They are used to determine the actual id via some auth/external mechanism. For `me` endpoints, think of the `me` directory being symlinked to your organization directory. For example, if you are a part of the organization `123`, the `me` directory will be symlinked to `/v1/organizations/123`:
```bash
v1/
├── organizations/
│ └── me/ -> symlink to /v1/organizations/123
│ └── users/
│ └── 123/
│ └── users/
```
> 💡 **Note**: There are various ways to structure endpoints. Some prefer to use singular resource names instead of `me`. Others prefer to use singular resource names for all endpoints. We have, however, chosen to standardize our endpoints in the manner described above.
## What should I remember?
- Use clear, **plural resource names**
- Use `me` endpoints for determining the actual id via some auth mechanism
> 💡 **Note**: When in doubt, diagram the relationships and walk through the user flows as if navigating a file system. This will help you design endpoints that are both logical and user-friendly.

View File

@@ -0,0 +1,106 @@
# Provider
SigNoz is built on the provider pattern, a design approach where code is organized into providers that handle specific application responsibilities. Providers act as adapter components that integrate with external services and deliver required functionality to the application.
> 💡 **Note**: Coming from a DDD background? Providers are similar (not exactly the same) to adapter/infrastructure services.
## How to create a new provider?
To create a new provider, create a directory in the `pkg/` directory named after your provider. The provider package consists of four key components:
- **Interface** (`pkg/<name>/<name>.go`): Defines the provider's interface. Other packages should import this interface to use the provider.
- **Config** (`pkg/<name>/config.go`): Contains provider configuration, implementing the `factory.Config` interface from [factory/config.go](/pkg/factory/config.go).
- **Implementation** (`pkg/<name>/<implname><name>/provider.go`): Contains the provider implementation, including a `NewProvider` function that returns a `factory.Provider` interface from [factory/provider.go](/pkg/factory/provider.go).
- **Mock** (`pkg/<name>/<name>test.go`): Provides mocks for the provider, typically used by dependent packages for unit testing.
For example, the [prometheus](/pkg/prometheus) provider delivers a prometheus engine to the application:
- `pkg/prometheus/prometheus.go` - Interface definition
- `pkg/prometheus/config.go` - Configuration
- `pkg/prometheus/clickhouseprometheus/provider.go` - Clickhouse-powered implementation
- `pkg/prometheus/prometheustest/provider.go` - Mock implementation
## How to wire it up?
The `pkg/signoz` package contains the inversion of control container responsible for wiring providers. It handles instantiation, configuration, and assembly of providers based on configuration metadata.
> 💡 **Note**: Coming from a Java background? Providers are similar to Spring beans.
Wiring up a provider involves three steps:
1. Wiring up the configuration
Add your config from `pkg/<name>/config.go` to the `pkg/signoz/config.Config` struct and in new factories:
```go
type Config struct {
...
MyProvider myprovider.Config `mapstructure:"myprovider"`
...
}
func NewConfig(ctx context.Context, resolverConfig config.ResolverConfig, ....) (Config, error) {
...
configFactories := []factory.ConfigFactory{
myprovider.NewConfigFactory(),
}
...
}
```
2. Wiring up the provider
Add available provider implementations in `pkg/signoz/provider.go`:
```go
func NewMyProviderFactories() factory.NamedMap[factory.ProviderFactory[myprovider.MyProvider, myprovider.Config]] {
return factory.MustNewNamedMap(
myproviderone.NewFactory(),
myprovidertwo.NewFactory(),
)
}
```
3. Instantiate the provider by adding it to the `SigNoz` struct in `pkg/signoz/signoz.go`:
```go
type SigNoz struct {
...
MyProvider myprovider.MyProvider
...
}
func New(...) (*SigNoz, error) {
...
myprovider, err := myproviderone.New(ctx, settings, config.MyProvider, "one/two")
if err != nil {
return nil, err
}
...
}
```
## How to use it?
To use a provider, import its interface. For example, to use the prometheus provider, import `pkg/prometheus/prometheus.go`:
```go
import "github.com/SigNoz/signoz/pkg/prometheus/prometheus"
func CreateSomething(ctx context.Context, prometheus prometheus.Prometheus) {
...
prometheus.DoSomething()
...
}
```
## Why do we need this?
Like any dependency injection framework, providers decouple the codebase from implementation details. This is especially valuable in SigNoz's large codebase, where we need to swap implementations without changing dependent code. The provider pattern offers several benefits apart from the obvious one of decoupling:
- Configuration is **defined with each provider and centralized in one place**, making it easier to understand and manage through various methods (environment variables, config files, etc.)
- Provider mocking is **straightforward for unit testing**, with a consistent pattern for locating mocks
- **Multiple implementations** of the same provider are **supported**, as demonstrated by our sqlstore provider
## What should I remember?
- Use the provider pattern wherever applicable.
- Always create a provider **irrespective of the number of implementations**. This makes it easier to add new implementations in the future.

View File

@@ -6,11 +6,13 @@ import (
"time"
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types/analyticstypes"
"github.com/SigNoz/signoz/pkg/types/licensetypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/SigNoz/signoz/pkg/zeus"
@@ -23,16 +25,17 @@ type provider struct {
config licensing.Config
settings factory.ScopedProviderSettings
orgGetter organization.Getter
analytics analytics.Analytics
stopChan chan struct{}
}
func NewProviderFactory(store sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
func NewProviderFactory(store sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter, analytics analytics.Analytics) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
return factory.NewProviderFactory(factory.MustNewName("http"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
return New(ctx, providerSettings, config, store, zeus, orgGetter)
return New(ctx, providerSettings, config, store, zeus, orgGetter, analytics)
})
}
func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Config, sqlstore sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter) (licensing.Licensing, error) {
func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Config, sqlstore sqlstore.SQLStore, zeus zeus.Zeus, orgGetter organization.Getter, analytics analytics.Analytics) (licensing.Licensing, error) {
settings := factory.NewScopedProviderSettings(ps, "github.com/SigNoz/signoz/ee/licensing/httplicensing")
licensestore := sqllicensingstore.New(sqlstore)
return &provider{
@@ -42,6 +45,7 @@ func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Conf
settings: settings,
orgGetter: orgGetter,
stopChan: make(chan struct{}),
analytics: analytics,
}, nil
}
@@ -159,6 +163,25 @@ func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUI
return err
}
stats := licensetypes.NewStatsFromLicense(activeLicense)
provider.analytics.Send(ctx,
analyticstypes.Track{
UserId: "stats_" + organizationID.String(),
Event: "License Updated",
Properties: analyticstypes.NewPropertiesFromMap(stats),
Context: &analyticstypes.Context{
Extra: map[string]interface{}{
analyticstypes.KeyGroupID: organizationID.String(),
},
},
},
analyticstypes.Group{
UserId: "stats_" + organizationID.String(),
GroupId: organizationID.String(),
Traits: analyticstypes.NewTraitsFromMap(stats),
},
)
return nil
}
@@ -211,3 +234,16 @@ func (provider *provider) GetFeatureFlags(ctx context.Context, organizationID va
return license.Features, nil
}
func (provider *provider) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
activeLicense, err := provider.GetActive(ctx, orgID)
if err != nil {
if errors.Ast(err, errors.TypeNotFound) {
return map[string]any{}, nil
}
return nil, err
}
return licensetypes.NewStatsFromLicense(activeLicense), nil
}

View File

@@ -39,6 +39,7 @@ builds:
- -X github.com/SigNoz/signoz/ee/zeus.deprecatedURL=https://license.signoz.io
- -X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud
- -X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1
- -X github.com/SigNoz/signoz/pkg/analytics.key=9kRrJ7oPCGPEJLF6QjMPLt5bljFhRQBr
- >-
{{- if eq .Os "linux" }}-linkmode external -extldflags '-static'{{- end }}
mod_timestamp: "{{ .CommitTimestamp }}"

View File

@@ -11,11 +11,9 @@ RUN apk update && \
COPY ./target/${OS}-${TARGETARCH}/signoz /root/signoz
COPY ./conf/prometheus.yml /root/config/prometheus.yml
COPY ./templates/email /root/templates
COPY frontend/build/ /etc/signoz/web/
RUN chmod 755 /root /root/signoz
ENTRYPOINT ["./signoz"]
CMD ["-config", "/root/config/prometheus.yml"]
ENTRYPOINT ["./signoz"]

View File

@@ -12,11 +12,9 @@ RUN apk update && \
rm -rf /var/cache/apk/*
COPY ./target/${OS}-${ARCH}/signoz /root/signoz
COPY ./conf/prometheus.yml /root/config/prometheus.yml
COPY ./templates/email /root/templates
COPY frontend/build/ /etc/signoz/web/
RUN chmod 755 /root /root/signoz
ENTRYPOINT ["./signoz"]
CMD ["-config", "/root/config/prometheus.yml"]

View File

@@ -7,15 +7,16 @@ import (
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
"github.com/SigNoz/signoz/ee/query-service/interfaces"
"github.com/SigNoz/signoz/ee/query-service/usage"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/apis/fields"
"github.com/SigNoz/signoz/pkg/http/middleware"
querierAPI "github.com/SigNoz/signoz/pkg/querier"
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
rules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/signoz"
@@ -25,8 +26,7 @@ import (
)
type APIHandlerOptions struct {
DataConnector interfaces.DataConnector
PreferSpanMetrics bool
DataConnector interfaces.Reader
RulesManager *rules.Manager
UsageManager *usage.Manager
IntegrationsController *integrations.Controller
@@ -50,7 +50,6 @@ type APIHandler struct {
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) {
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector,
PreferSpanMetrics: opts.PreferSpanMetrics,
RuleManager: opts.RulesManager,
IntegrationsController: opts.IntegrationsController,
CloudIntegrationsController: opts.CloudIntegrationsController,
@@ -58,8 +57,9 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
FluxInterval: opts.FluxInterval,
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
LicensingAPI: httplicensing.NewLicensingAPI(signoz.Licensing),
FieldsAPI: fields.NewAPI(signoz.TelemetryStore, signoz.Instrumentation.Logger()),
FieldsAPI: fields.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.TelemetryStore),
Signoz: signoz,
QuerierAPI: querierAPI.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.Querier),
})
if err != nil {

View File

@@ -96,7 +96,7 @@ func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
return
}
nextPage, err := ah.Signoz.Modules.User.PrepareSsoRedirect(ctx, redirectUri, email, ah.opts.JWT)
nextPage, err := ah.Signoz.Modules.User.PrepareSsoRedirect(ctx, redirectUri, email)
if err != nil {
zap.L().Error("[receiveSAML] failed to generate redirect URI after successful login ", zap.String("domain", domain.String()), zap.Error(err))
handleSsoError(w, r, redirectUri)

View File

@@ -59,7 +59,7 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
}
}
if ah.opts.PreferSpanMetrics {
if constants.IsPreferSpanMetrics {
for idx, feature := range featureSet {
if feature.Name == licensetypes.UseSpanMetrics {
featureSet[idx].Active = true

View File

@@ -1,39 +0,0 @@
package db
import (
"time"
"github.com/ClickHouse/clickhouse-go/v2"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/prometheus"
basechr "github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
)
type ClickhouseReader struct {
conn clickhouse.Conn
appdb sqlstore.SQLStore
*basechr.ClickHouseReader
}
func NewDataConnector(
sqlDB sqlstore.SQLStore,
telemetryStore telemetrystore.TelemetryStore,
prometheus prometheus.Prometheus,
cluster string,
fluxIntervalForTraceDetail time.Duration,
cache cache.Cache,
) *ClickhouseReader {
chReader := basechr.NewReader(sqlDB, telemetryStore, prometheus, cluster, fluxIntervalForTraceDetail, cache)
return &ClickhouseReader{
conn: telemetryStore.ClickhouseDB(),
appdb: sqlDB,
ClickHouseReader: chReader,
}
}
func (r *ClickhouseReader) GetSQLStore() sqlstore.SQLStore {
return r.appdb
}

View File

@@ -6,14 +6,10 @@ import (
"net"
"net/http"
_ "net/http/pprof" // http profiler
"time"
"github.com/gorilla/handlers"
"github.com/jmoiron/sqlx"
"github.com/SigNoz/signoz/ee/query-service/app/api"
"github.com/SigNoz/signoz/ee/query-service/app/db"
"github.com/SigNoz/signoz/ee/query-service/constants"
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
"github.com/SigNoz/signoz/ee/query-service/rules"
"github.com/SigNoz/signoz/ee/query-service/usage"
@@ -32,6 +28,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
@@ -41,7 +38,6 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/healthcheck"
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
"github.com/SigNoz/signoz/pkg/query-service/utils"
"go.uber.org/zap"
)
@@ -59,62 +55,55 @@ type ServerOptions struct {
Jwt *authtypes.JWT
}
// Server runs HTTP api service
// Server runs HTTP, Mux and a grpc server
type Server struct {
serverOptions *ServerOptions
ruleManager *baserules.Manager
config signoz.Config
signoz *signoz.SigNoz
jwt *authtypes.JWT
ruleManager *baserules.Manager
// public http router
httpConn net.Listener
httpServer *http.Server
httpConn net.Listener
httpServer *http.Server
httpHostPort string
// private http
privateConn net.Listener
privateHTTP *http.Server
privateConn net.Listener
privateHTTP *http.Server
privateHostPort string
opampServer *opamp.Server
// Usage manager
usageManager *usage.Manager
opampServer *opamp.Server
unavailableChannel chan healthcheck.Status
}
// HealthCheckStatus returns health check status channel a client can subscribe to
func (s Server) HealthCheckStatus() chan healthcheck.Status {
return s.unavailableChannel
}
// NewServer creates and initializes Server
func NewServer(serverOptions *ServerOptions) (*Server, error) {
gatewayProxy, err := gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
func NewServer(config signoz.Config, signoz *signoz.SigNoz, jwt *authtypes.JWT) (*Server, error) {
gatewayProxy, err := gateway.NewProxy(config.Gateway.URL.String(), gateway.RoutePrefix)
if err != nil {
return nil, err
}
fluxIntervalForTraceDetail, err := time.ParseDuration(serverOptions.FluxIntervalForTraceDetail)
if err != nil {
return nil, err
}
reader := db.NewDataConnector(
serverOptions.SigNoz.SQLStore,
serverOptions.SigNoz.TelemetryStore,
serverOptions.SigNoz.Prometheus,
serverOptions.Cluster,
fluxIntervalForTraceDetail,
serverOptions.SigNoz.Cache,
reader := clickhouseReader.NewReader(
signoz.SQLStore,
signoz.TelemetryStore,
signoz.Prometheus,
signoz.TelemetryStore.Cluster(),
config.Querier.FluxInterval,
signoz.Cache,
)
rm, err := makeRulesManager(
serverOptions.SigNoz.SQLStore.SQLxDB(),
reader,
serverOptions.SigNoz.Cache,
serverOptions.SigNoz.Alertmanager,
serverOptions.SigNoz.SQLStore,
serverOptions.SigNoz.TelemetryStore,
serverOptions.SigNoz.Prometheus,
serverOptions.SigNoz.Modules.OrgGetter,
signoz.Cache,
signoz.Alertmanager,
signoz.SQLStore,
signoz.TelemetryStore,
signoz.Prometheus,
signoz.Modules.OrgGetter,
)
if err != nil {
@@ -122,19 +111,16 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
// initiate opamp
_, err = opAmpModel.InitDB(serverOptions.SigNoz.SQLStore.SQLxDB())
if err != nil {
return nil, err
}
opAmpModel.Init(signoz.SQLStore, signoz.Instrumentation.Logger(), signoz.Modules.OrgGetter)
integrationsController, err := integrations.NewController(serverOptions.SigNoz.SQLStore)
integrationsController, err := integrations.NewController(signoz.SQLStore)
if err != nil {
return nil, fmt.Errorf(
"couldn't create integrations controller: %w", err,
)
}
cloudIntegrationsController, err := cloudintegrations.NewController(serverOptions.SigNoz.SQLStore)
cloudIntegrationsController, err := cloudintegrations.NewController(signoz.SQLStore)
if err != nil {
return nil, fmt.Errorf(
"couldn't create cloud provider integrations controller: %w", err,
@@ -143,7 +129,8 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
// ingestion pipelines manager
logParsingPipelineController, err := logparsingpipeline.NewLogParsingPipelinesController(
serverOptions.SigNoz.SQLStore, integrationsController.GetPipelinesForInstalledIntegrations,
signoz.SQLStore,
integrationsController.GetPipelinesForInstalledIntegrations,
)
if err != nil {
return nil, err
@@ -151,7 +138,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
// initiate agent config handler
agentConfMgr, err := agentConf.Initiate(&agentConf.ManagerOptions{
DB: serverOptions.SigNoz.SQLStore.SQLxDB(),
Store: signoz.SQLStore,
AgentFeatures: []agentConf.AgentFeature{logParsingPipelineController},
})
if err != nil {
@@ -159,7 +146,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
}
// start the usagemanager
usageManager, err := usage.New(serverOptions.SigNoz.Licensing, serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.SigNoz.Zeus, serverOptions.SigNoz.Modules.OrgGetter)
usageManager, err := usage.New(signoz.Licensing, signoz.TelemetryStore.ClickhouseDB(), signoz.Zeus, signoz.Modules.OrgGetter)
if err != nil {
return nil, err
}
@@ -168,47 +155,36 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err
}
telemetry.GetInstance().SetReader(reader)
telemetry.GetInstance().SetSqlStore(serverOptions.SigNoz.SQLStore)
telemetry.GetInstance().SetSaasOperator(constants.SaasSegmentKey)
telemetry.GetInstance().SetSavedViewsInfoCallback(telemetry.GetSavedViewsInfo)
telemetry.GetInstance().SetAlertsInfoCallback(telemetry.GetAlertsInfo)
telemetry.GetInstance().SetGetUsersCallback(telemetry.GetUsers)
telemetry.GetInstance().SetUserCountCallback(telemetry.GetUserCount)
telemetry.GetInstance().SetDashboardsInfoCallback(telemetry.GetDashboardsInfo)
fluxInterval, err := time.ParseDuration(serverOptions.FluxInterval)
if err != nil {
return nil, err
}
apiOpts := api.APIHandlerOptions{
DataConnector: reader,
PreferSpanMetrics: serverOptions.PreferSpanMetrics,
RulesManager: rm,
UsageManager: usageManager,
IntegrationsController: integrationsController,
CloudIntegrationsController: cloudIntegrationsController,
LogsParsingPipelineController: logParsingPipelineController,
FluxInterval: fluxInterval,
FluxInterval: config.Querier.FluxInterval,
Gateway: gatewayProxy,
GatewayUrl: serverOptions.GatewayUrl,
JWT: serverOptions.Jwt,
GatewayUrl: config.Gateway.URL.String(),
JWT: jwt,
}
apiHandler, err := api.NewAPIHandler(apiOpts, serverOptions.SigNoz)
apiHandler, err := api.NewAPIHandler(apiOpts, signoz)
if err != nil {
return nil, err
}
s := &Server{
config: config,
signoz: signoz,
jwt: jwt,
ruleManager: rm,
serverOptions: serverOptions,
httpHostPort: baseconst.HTTPHostPort,
privateHostPort: baseconst.PrivateHostPort,
unavailableChannel: make(chan healthcheck.Status),
usageManager: usageManager,
}
httpServer, err := s.createPublicServer(apiHandler, serverOptions.SigNoz.Web)
httpServer, err := s.createPublicServer(apiHandler, signoz.Web)
if err != nil {
return nil, err
@@ -224,7 +200,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
s.privateHTTP = privateServer
s.opampServer = opamp.InitializeServer(
&opAmpModel.AllAgents, agentConfMgr,
&opAmpModel.AllAgents, agentConfMgr, signoz.Instrumentation,
)
orgs, err := apiHandler.Signoz.Modules.OrgGetter.ListByOwnedKeyRange(context.Background())
@@ -241,18 +217,22 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return s, nil
}
// HealthCheckStatus returns health check status channel a client can subscribe to
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.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}, s.serverOptions.SigNoz.Sharder, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.SigNoz.Sharder).Wrap)
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
s.serverOptions.Config.APIServer.Timeout.Default,
s.serverOptions.Config.APIServer.Timeout.Max,
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.NewAnalytics().Wrap)
r.Use(middleware.NewLogging(s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.Config.APIServer.Logging.ExcludedRoutes).Wrap)
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
apiHandler.RegisterPrivateRoutes(r)
@@ -274,17 +254,16 @@ func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server,
func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*http.Server, error) {
r := baseapp.NewRouter()
am := middleware.NewAuthZ(s.serverOptions.SigNoz.Instrumentation.Logger())
am := middleware.NewAuthZ(s.signoz.Instrumentation.Logger())
r.Use(middleware.NewAuth(s.serverOptions.Jwt, []string{"Authorization", "Sec-WebSocket-Protocol"}, s.serverOptions.SigNoz.Sharder, s.serverOptions.SigNoz.Instrumentation.Logger()).Wrap)
r.Use(middleware.NewAPIKey(s.serverOptions.SigNoz.SQLStore, []string{"SIGNOZ-API-KEY"}, s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.SigNoz.Sharder).Wrap)
r.Use(middleware.NewTimeout(s.serverOptions.SigNoz.Instrumentation.Logger(),
s.serverOptions.Config.APIServer.Timeout.ExcludedRoutes,
s.serverOptions.Config.APIServer.Timeout.Default,
s.serverOptions.Config.APIServer.Timeout.Max,
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.NewAnalytics().Wrap)
r.Use(middleware.NewLogging(s.serverOptions.SigNoz.Instrumentation.Logger(), s.serverOptions.Config.APIServer.Logging.ExcludedRoutes).Wrap)
r.Use(middleware.NewLogging(s.signoz.Instrumentation.Logger(), s.config.APIServer.Logging.ExcludedRoutes).Wrap)
apiHandler.RegisterRoutes(r, am)
apiHandler.RegisterLogsRoutes(r, am)
@@ -294,6 +273,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
apiHandler.RegisterQueryRangeV3Routes(r, am)
apiHandler.RegisterInfraMetricsRoutes(r, am)
apiHandler.RegisterQueryRangeV4Routes(r, am)
apiHandler.RegisterQueryRangeV5Routes(r, am)
apiHandler.RegisterWebSocketPaths(r, am)
apiHandler.RegisterMessagingQueuesRoutes(r, am)
apiHandler.RegisterThirdPartyApiRoutes(r, am)
@@ -324,7 +304,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
func (s *Server) initListeners() error {
// listen on public port
var err error
publicHostPort := s.serverOptions.HTTPHostPort
publicHostPort := s.httpHostPort
if publicHostPort == "" {
return fmt.Errorf("baseconst.HTTPHostPort is required")
}
@@ -334,10 +314,10 @@ func (s *Server) initListeners() error {
return err
}
zap.L().Info(fmt.Sprintf("Query server started listening on %s...", s.serverOptions.HTTPHostPort))
zap.L().Info(fmt.Sprintf("Query server started listening on %s...", s.httpHostPort))
// listen on private port to support internal services
privateHostPort := s.serverOptions.PrivateHostPort
privateHostPort := s.privateHostPort
if privateHostPort == "" {
return fmt.Errorf("baseconst.PrivateHostPort is required")
@@ -347,7 +327,7 @@ func (s *Server) initListeners() error {
if err != nil {
return err
}
zap.L().Info(fmt.Sprintf("Query server started listening on private port %s...", s.serverOptions.PrivateHostPort))
zap.L().Info(fmt.Sprintf("Query server started listening on private port %s...", s.privateHostPort))
return nil
}
@@ -367,7 +347,7 @@ func (s *Server) Start(ctx context.Context) error {
}
go func() {
zap.L().Info("Starting HTTP server", zap.Int("port", httpPort), zap.String("addr", s.serverOptions.HTTPHostPort))
zap.L().Info("Starting HTTP server", zap.Int("port", httpPort), zap.String("addr", s.httpHostPort))
switch err := s.httpServer.Serve(s.httpConn); err {
case nil, http.ErrServerClosed, cmux.ErrListenerClosed:
@@ -393,7 +373,7 @@ func (s *Server) Start(ctx context.Context) error {
}
go func() {
zap.L().Info("Starting Private HTTP server", zap.Int("port", privatePort), zap.String("addr", s.serverOptions.PrivateHostPort))
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:
@@ -445,7 +425,6 @@ func (s *Server) Stop(ctx context.Context) error {
}
func makeRulesManager(
db *sqlx.DB,
ch baseint.Reader,
cache cache.Cache,
alertmanager alertmanager.Alertmanager,
@@ -458,7 +437,6 @@ func makeRulesManager(
managerOpts := &baserules.ManagerOptions{
TelemetryStore: telemetryStore,
Prometheus: prometheus,
DBConn: db,
Context: context.Background(),
Logger: zap.L(),
Reader: ch,

View File

@@ -37,9 +37,14 @@ func GetDefaultSiteURL() string {
const DotMetricsEnabled = "DOT_METRICS_ENABLED"
var IsDotMetricsEnabled = false
var IsPreferSpanMetrics = false
func init() {
if GetOrDefaultEnv(DotMetricsEnabled, "false") == "true" {
IsDotMetricsEnabled = true
}
if GetOrDefaultEnv("USE_SPAN_METRICS", "false") == "true" {
IsPreferSpanMetrics = true
}
}

View File

@@ -1,11 +0,0 @@
package interfaces
import (
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
)
// Connector defines methods for interaction
// with o11y data. for example - clickhouse
type DataConnector interface {
baseint.Reader
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
"github.com/SigNoz/signoz/ee/zeus"
"github.com/SigNoz/signoz/ee/zeus/httpzeus"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/config"
"github.com/SigNoz/signoz/pkg/config/envprovider"
"github.com/SigNoz/signoz/pkg/config/fileprovider"
@@ -101,10 +102,14 @@ func main() {
fileprovider.NewFactory(),
},
}, signoz.DeprecatedFlags{
MaxIdleConns: maxIdleConns,
MaxOpenConns: maxOpenConns,
DialTimeout: dialTimeout,
Config: promConfigPath,
MaxIdleConns: maxIdleConns,
MaxOpenConns: maxOpenConns,
DialTimeout: dialTimeout,
Config: promConfigPath,
FluxInterval: fluxInterval,
FluxIntervalForTraceDetail: fluxIntervalForTraceDetail,
Cluster: cluster,
GatewayUrl: gatewayUrl,
})
if err != nil {
zap.L().Fatal("Failed to create config", zap.Error(err))
@@ -134,8 +139,8 @@ func main() {
zeus.Config(),
httpzeus.NewProviderFactory(),
licensing.Config(24*time.Hour, 3),
func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus, orgGetter organization.Getter) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] {
return httplicensing.NewProviderFactory(sqlstore, zeus, orgGetter)
func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus, orgGetter organization.Getter, analytics analytics.Analytics) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] {
return httplicensing.NewProviderFactory(sqlstore, zeus, orgGetter, analytics)
},
signoz.NewEmailingProviderFactories(),
signoz.NewCacheProviderFactories(),
@@ -147,20 +152,7 @@ func main() {
zap.L().Fatal("Failed to create signoz", zap.Error(err))
}
serverOptions := &app.ServerOptions{
Config: config,
SigNoz: signoz,
HTTPHostPort: baseconst.HTTPHostPort,
PreferSpanMetrics: preferSpanMetrics,
PrivateHostPort: baseconst.PrivateHostPort,
FluxInterval: fluxInterval,
FluxIntervalForTraceDetail: fluxIntervalForTraceDetail,
Cluster: cluster,
GatewayUrl: gatewayUrl,
Jwt: jwt,
}
server, err := app.NewServer(serverOptions)
server, err := app.NewServer(config, signoz, jwt)
if err != nil {
zap.L().Fatal("Failed to create server", zap.Error(err))
}

View File

@@ -17,19 +17,21 @@ var (
)
var (
Org = "org"
User = "user"
UserNoCascade = "user_no_cascade"
FactorPassword = "factor_password"
CloudIntegration = "cloud_integration"
Org = "org"
User = "user"
UserNoCascade = "user_no_cascade"
FactorPassword = "factor_password"
CloudIntegration = "cloud_integration"
AgentConfigVersion = "agent_config_version"
)
var (
OrgReference = `("org_id") REFERENCES "organizations" ("id")`
UserReference = `("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE`
UserReferenceNoCascade = `("user_id") REFERENCES "users" ("id")`
FactorPasswordReference = `("password_id") REFERENCES "factor_password" ("id")`
CloudIntegrationReference = `("cloud_integration_id") REFERENCES "cloud_integration" ("id") ON DELETE CASCADE`
OrgReference = `("org_id") REFERENCES "organizations" ("id")`
UserReference = `("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE`
UserReferenceNoCascade = `("user_id") REFERENCES "users" ("id")`
FactorPasswordReference = `("password_id") REFERENCES "factor_password" ("id")`
CloudIntegrationReference = `("cloud_integration_id") REFERENCES "cloud_integration" ("id") ON DELETE CASCADE`
AgentConfigVersionReference = `("version_id") REFERENCES "agent_config_version" ("id")`
)
type dialect struct{}
@@ -274,6 +276,8 @@ func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.I
fkReferences = append(fkReferences, FactorPasswordReference)
} else if reference == CloudIntegration && !slices.Contains(fkReferences, CloudIntegrationReference) {
fkReferences = append(fkReferences, CloudIntegrationReference)
} else if reference == AgentConfigVersion && !slices.Contains(fkReferences, AgentConfigVersionReference) {
fkReferences = append(fkReferences, AgentConfigVersionReference)
}
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/jackc/pgx/v5/stdlib"
"github.com/jmoiron/sqlx"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
)
@@ -19,7 +18,6 @@ type provider struct {
settings factory.ScopedProviderSettings
sqldb *sql.DB
bundb *sqlstore.BunDB
sqlxdb *sqlx.DB
dialect *dialect
}
@@ -61,7 +59,6 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
settings: settings,
sqldb: sqldb,
bundb: sqlstore.NewBunDB(settings, sqldb, pgdialect.New(), hooks),
sqlxdb: sqlx.NewDb(sqldb, "postgres"),
dialect: new(dialect),
}, nil
}
@@ -74,10 +71,6 @@ func (provider *provider) SQLDB() *sql.DB {
return provider.sqldb
}
func (provider *provider) SQLxDB() *sqlx.DB {
return provider.sqlxdb
}
func (provider *provider) Dialect() sqlstore.SQLDialect {
return provider.dialect
}

View File

@@ -1,5 +1,5 @@
module.exports = {
ignorePatterns: ['src/antlr-parser/*.ts'],
ignorePatterns: ['src/parser/*.ts'],
env: {
browser: true,
es2021: true,

View File

@@ -32,7 +32,7 @@ import { getQueryContextAtCursor } from 'utils/queryContextUtils';
2. Context-aware autocompletion
3. Support for various query operators (=, !=, IN, LIKE, etc.)
4. Support for complex conditions with AND/OR operators
5. Support for functions (HAS, HASANY, HASALL, HASNONE)
5. Support for functions (HAS, HASANY, HASALL)
6. Support for parentheses and nested conditions
7. Query examples for common use cases

View File

@@ -83,7 +83,7 @@
"fontfaceobserver": "2.3.0",
"history": "4.10.1",
"html-webpack-plugin": "5.5.0",
"http-proxy-middleware": "3.0.3",
"http-proxy-middleware": "3.0.5",
"http-status-codes": "2.3.0",
"i18next": "^21.6.12",
"i18next-browser-languagedetector": "^6.1.3",
@@ -139,7 +139,7 @@
"uuid": "^8.3.2",
"web-vitals": "^0.2.4",
"webpack": "5.94.0",
"webpack-dev-server": "^4.15.2",
"webpack-dev-server": "^5.2.1",
"webpack-retry-chunk-load-plugin": "3.1.1",
"xstate": "^4.31.0"
},
@@ -202,7 +202,6 @@
"babel-plugin-styled-components": "^1.12.0",
"compression-webpack-plugin": "9.0.0",
"copy-webpack-plugin": "^11.0.0",
"critters-webpack-plugin": "^3.0.1",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^16.1.4",
@@ -240,7 +239,7 @@
"ts-node": "^10.2.1",
"typescript-plugin-css-modules": "5.0.1",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^4.9.2"
"webpack-cli": "^5.1.4"
},
"lint-staged": {
"*.(js|jsx|ts|tsx)": [
@@ -256,7 +255,7 @@
"xml2js": "0.5.0",
"phin": "^3.7.1",
"body-parser": "1.20.3",
"http-proxy-middleware": "3.0.3",
"http-proxy-middleware": "3.0.5",
"cross-spawn": "7.0.5",
"cookie": "^0.7.1",
"serialize-javascript": "6.0.2",

View File

@@ -9,8 +9,8 @@
"tooltip_notification_channels": "More details on how to setting notification channels",
"sending_channels_note": "The alerts will be sent to all the configured channels.",
"loading_channels_message": "Loading Channels..",
"page_title_create": "New Notification Channels",
"page_title_edit": "Edit Notification Channels",
"page_title_create": "New Notification Channel",
"page_title_edit": "Edit Notification Channel",
"button_save_channel": "Save",
"button_test_channel": "Test",
"button_return": "Back",
@@ -62,5 +62,8 @@
"channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly",
"channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again",
"webhook_url_required": "Webhook URL is mandatory",
"slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)"
"slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)",
"api_key_required": "API Key is mandatory",
"to_required": "To field is mandatory",
"channel_name_required": "Channel name is mandatory"
}

View File

@@ -129,5 +129,6 @@
"text_num_points": "data points in each result group",
"text_alert_frequency": "Run alert every",
"text_for": "minutes",
"selected_query_placeholder": "Select query"
"selected_query_placeholder": "Select query",
"alert_rule_not_found": "Alert Rule not found"
}

View File

@@ -9,8 +9,8 @@
"tooltip_notification_channels": "More details on how to setting notification channels",
"sending_channels_note": "The alerts will be sent to all the configured channels.",
"loading_channels_message": "Loading Channels..",
"page_title_create": "New Notification Channels",
"page_title_edit": "Edit Notification Channels",
"page_title_create": "New Notification Channel",
"page_title_edit": "Edit Notification Channel",
"button_save_channel": "Save",
"button_test_channel": "Test",
"button_return": "Back",
@@ -77,5 +77,8 @@
"channel_test_failed": "Failed to send a test message to this channel, please confirm that the parameters are set correctly",
"channel_test_unexpected": "An unexpected error occurred while sending a message to this channel, please try again",
"webhook_url_required": "Webhook URL is mandatory",
"slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)"
"slack_channel_help": "Specify channel or user, use #channel-name, @username (has to be all lowercase, no whitespace)",
"api_key_required": "API Key is mandatory",
"to_required": "To field is mandatory",
"channel_name_required": "Channel name is mandatory"
}

View File

@@ -3,6 +3,7 @@ import setLocalStorageApi from 'api/browser/localstorage/set';
import getAll from 'api/v1/user/get';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import { ORG_PREFERENCES } from 'constants/orgPreferences';
import ROUTES from 'constants/routes';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import history from 'lib/history';
@@ -14,6 +15,7 @@ import { matchPath, useLocation } from 'react-router-dom';
import { SuccessResponseV2 } from 'types/api';
import APIError from 'types/api/error';
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
import { OrgPreference } from 'types/api/preferences/preference';
import { Organization } from 'types/api/user/getOrganization';
import { UserResponse } from 'types/api/user/getUser';
import { USER_ROLES } from 'types/roles';
@@ -95,7 +97,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
usersData.data
) {
const isOnboardingComplete = orgPreferences?.find(
(preference: Record<string, any>) => preference.key === 'ORG_ONBOARDING',
(preference: OrgPreference) =>
preference.name === ORG_PREFERENCES.ORG_ONBOARDING,
)?.value;
const isFirstUser = checkFirstTimeUser();
@@ -123,7 +126,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
const isRouteEnabledForWorkspaceBlockedState =
isAdmin &&
(path === ROUTES.ORG_SETTINGS ||
(path === ROUTES.SETTINGS ||
path === ROUTES.ORG_SETTINGS ||
path === ROUTES.BILLING ||
path === ROUTES.MY_SETTINGS);

View File

@@ -71,7 +71,7 @@ function App(): JSX.Element {
const orgName =
org && Array.isArray(org) && org.length > 0 ? org[0].displayName : '';
const { displayName, email, role } = user;
const { displayName, email, role, id, orgId } = user;
const domain = extractDomain(email);
const hostNameParts = hostname.split('.');
@@ -105,7 +105,7 @@ function App(): JSX.Element {
logEvent('Domain Identified', groupTraits, 'group');
}
if (window && window.Appcues) {
window.Appcues.identify(email, {
window.Appcues.identify(id, {
name: displayName,
tenant_id: hostNameParts[0],
@@ -131,7 +131,7 @@ function App(): JSX.Element {
isPaidUser: !!trialInfo?.trialConvertedToSubscription,
});
posthog?.identify(email, {
posthog?.identify(id, {
email,
name: displayName,
orgName,
@@ -143,7 +143,7 @@ function App(): JSX.Element {
isPaidUser: !!trialInfo?.trialConvertedToSubscription,
});
posthog?.group('company', domain, {
posthog?.group('company', orgId, {
name: orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
@@ -193,11 +193,12 @@ function App(): JSX.Element {
updatedRoutes = updatedRoutes.filter(
(route) => route?.path !== ROUTES.BILLING,
);
if (isEnterpriseSelfHostedUser) {
updatedRoutes.push(LIST_LICENSES);
}
}
if (isEnterpriseSelfHostedUser) {
updatedRoutes.push(LIST_LICENSES);
}
// always add support route for cloud users
updatedRoutes = [...updatedRoutes, SUPPORT_ROUTE];
} else {

View File

@@ -128,12 +128,7 @@ export const AlertOverview = Loadable(
);
export const CreateAlertChannelAlerts = Loadable(
() =>
import(/* webpackChunkName: "Create Channels" */ 'pages/AlertChannelCreate'),
);
export const EditAlertChannelsAlerts = Loadable(
() => import(/* webpackChunkName: "Edit Channels" */ 'pages/ChannelsEdit'),
() => import(/* webpackChunkName: "Create Channels" */ 'pages/Settings'),
);
export const AllAlertChannels = Loadable(
@@ -165,7 +160,7 @@ export const APIKeys = Loadable(
);
export const MySettings = Loadable(
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
() => import(/* webpackChunkName: "All MySettings" */ 'pages/Settings'),
);
export const CustomDomainSettings = Loadable(
@@ -222,7 +217,7 @@ export const LogsIndexToFields = Loadable(
);
export const BillingPage = Loadable(
() => import(/* webpackChunkName: "BillingPage" */ 'pages/Billing'),
() => import(/* webpackChunkName: "BillingPage" */ 'pages/Settings'),
);
export const SupportPage = Loadable(
@@ -249,7 +244,7 @@ export const WorkspaceAccessRestricted = Loadable(
);
export const ShortcutsPage = Loadable(
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Shortcuts'),
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Settings'),
);
export const InstalledIntegrations = Loadable(

View File

@@ -7,20 +7,15 @@ import {
AlertOverview,
AllAlertChannels,
AllErrors,
APIKeys,
ApiMonitoring,
BillingPage,
CreateAlertChannelAlerts,
CreateNewAlerts,
CustomDomainSettings,
DashboardPage,
DashboardWidget,
EditAlertChannelsAlerts,
EditRulesPage,
ErrorDetails,
Home,
InfrastructureMonitoring,
IngestionSettings,
InstalledIntegrations,
LicensePage,
ListAllALertsPage,
@@ -31,12 +26,10 @@ import {
LogsIndexToFields,
LogsSaveViews,
MetricsExplorer,
MySettings,
NewDashboardPage,
OldLogsExplorer,
Onboarding,
OnboardingV2,
OrganizationSettings,
OrgOnboarding,
PasswordReset,
PipelinePage,
@@ -45,7 +38,6 @@ import {
ServicesTablePage,
ServiceTopLevelOperationsPage,
SettingsPage,
ShortcutsPage,
SignupPage,
SomethingWentWrong,
StatusPage,
@@ -150,7 +142,7 @@ const routes: AppRoutes[] = [
},
{
path: ROUTES.SETTINGS,
exact: true,
exact: false,
component: SettingsPage,
isPrivate: true,
key: 'SETTINGS',
@@ -260,13 +252,6 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'CHANNELS_NEW',
},
{
path: ROUTES.CHANNELS_EDIT,
exact: true,
component: EditAlertChannelsAlerts,
isPrivate: true,
key: 'CHANNELS_EDIT',
},
{
path: ROUTES.ALL_CHANNELS,
exact: true,
@@ -295,41 +280,6 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'VERSION',
},
{
path: ROUTES.ORG_SETTINGS,
exact: true,
component: OrganizationSettings,
isPrivate: true,
key: 'ORG_SETTINGS',
},
{
path: ROUTES.INGESTION_SETTINGS,
exact: true,
component: IngestionSettings,
isPrivate: true,
key: 'INGESTION_SETTINGS',
},
{
path: ROUTES.API_KEYS,
exact: true,
component: APIKeys,
isPrivate: true,
key: 'API_KEYS',
},
{
path: ROUTES.MY_SETTINGS,
exact: true,
component: MySettings,
isPrivate: true,
key: 'MY_SETTINGS',
},
{
path: ROUTES.CUSTOM_DOMAIN_SETTINGS,
exact: true,
component: CustomDomainSettings,
isPrivate: true,
key: 'CUSTOM_DOMAIN_SETTINGS',
},
{
path: ROUTES.LOGS,
exact: true,
@@ -393,13 +343,6 @@ const routes: AppRoutes[] = [
key: 'SOMETHING_WENT_WRONG',
isPrivate: false,
},
{
path: ROUTES.BILLING,
exact: true,
component: BillingPage,
key: 'BILLING',
isPrivate: true,
},
{
path: ROUTES.WORKSPACE_LOCKED,
exact: true,
@@ -421,13 +364,6 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'WORKSPACE_ACCESS_RESTRICTED',
},
{
path: ROUTES.SHORTCUTS,
exact: true,
component: ShortcutsPage,
isPrivate: true,
key: 'SHORTCUTS',
},
{
path: ROUTES.INTEGRATIONS,
exact: true,

View File

@@ -1,183 +0,0 @@
grammar FilterQuery;
/*
* Parser Rules
*/
query
: expression ( (AND | OR) expression | expression )*
EOF
;
expression
: orExpression
;
orExpression
: andExpression ( OR andExpression )*
;
andExpression
: unaryExpression ( AND unaryExpression | unaryExpression )*
;
unaryExpression
: NOT? primary
;
primary
: LPAREN orExpression RPAREN
| comparison
| functionCall
| fullText
;
comparison
: key EQUALS value
| key (NOT_EQUALS | NEQ) value
| key LT value
| key LE value
| key GT value
| key GE value
| key (LIKE | ILIKE) value
| key (NOT_LIKE | NOT_ILIKE) value
| key BETWEEN value AND value
| key NOT_BETWEEN value AND value
| key inClause
| key notInClause
| key EXISTS
| key NOT_EXISTS
| key REGEXP value
| key NOT_REGEXP value
| key CONTAINS value
| key NOT_CONTAINS value
;
inClause
: IN LPAREN valueList RPAREN
| IN LBRACK valueList RBRACK
;
notInClause
: NOT_IN LPAREN valueList RPAREN
| NOT_IN LBRACK valueList RBRACK
;
valueList
: value ( COMMA value )*
;
fullText
: QUOTED_TEXT
;
functionCall
: (HAS | HASANY | HASALL | HASNONE) LPAREN functionParamList RPAREN
;
functionParamList
: functionParam ( COMMA functionParam )*
;
functionParam
: key
| value
| array
;
array
: LBRACK valueList RBRACK
;
value
: QUOTED_TEXT
| NUMBER
| BOOL
;
key
: KEY
;
/*
* Lexer Rules
*/
// Common punctuation / symbols
LPAREN : '(' ;
RPAREN : ')' ;
LBRACK : '[' ;
RBRACK : ']' ;
COMMA : ',' ;
EQUALS : '=' | '==' ;
NOT_EQUALS : '!=' ;
NEQ : '<>' ;
LT : '<' ;
LE : '<=' ;
GT : '>' ;
GE : '>=' ;
// Multi-keyword operators
LIKE : [Ll][Ii][Kk][Ee] ;
NOT_LIKE : [Nn][Oo][Tt] [ \t]+ [Ll][Ii][Kk][Ee] ;
ILIKE : [Ii][Ll][Ii][Kk][Ee] ;
NOT_ILIKE : [Nn][Oo][Tt] [ \t]+ [Ii][Ll][Ii][Kk][Ee] ;
BETWEEN : [Bb][Ee][Tt][Ww][Ee][Ee][Nn] ;
NOT_BETWEEN : [Nn][Oo][Tt] [ \t]+ [Bb][Ee][Tt][Ww][Ee][Ee][Nn] ;
EXISTS : [Ee][Xx][Ii][Ss][Tt][Ss]? ;
NOT_EXISTS : [Nn][Oo][Tt] [ \t]+ [Ee][Xx][Ii][Ss][Tt][Ss]? ;
REGEXP : [Rr][Ee][Gg][Ee][Xx][Pp] ;
NOT_REGEXP : [Nn][Oo][Tt] [ \t]+ [Rr][Ee][Gg][Ee][Xx][Pp] ;
CONTAINS : [Cc][Oo][Nn][Tt][Aa][Ii][Nn][Ss]? ;
NOT_CONTAINS: [Nn][Oo][Tt] [ \t]+ [Cc][Oo][Nn][Tt][Aa][Ii][Nn][Ss]? ;
IN : [Ii][Nn] ;
NOT_IN : [Nn][Oo][Tt] [ \t]+ [Ii][Nn] ;
// Boolean logic
NOT : [Nn][Oo][Tt] ;
AND : [Aa][Nn][Dd] ;
OR : [Oo][Rr] ;
// Functions
HAS : [Hh][Aa][Ss] ;
HASANY : [Hh][Aa][Ss][Aa][Nn][Yy] ;
HASALL : [Hh][Aa][Ss][Aa][Ll][Ll] ;
HASNONE : [Hh][Aa][Ss][Nn][Oo][Nn][Ee] ;
// Boolean values
BOOL
: [Tt][Rr][Uu][Ee]
| [Ff][Aa][Ll][Ss][Ee]
;
// Numbers
NUMBER
: DIGIT+ ( '.' DIGIT+ )?
;
// Quoted text
QUOTED_TEXT
: ( '"' ( ~["\\] | '\\' . )* '"' // double-quoted
| '\'' ( ~['\\] | '\\' . )* '\'' // single-quoted
)
;
// Keys
KEY
: [a-zA-Z0-9_] [a-zA-Z0-9_.[\]]*
;
// Whitespace
WS
: [ \t\r\n]+ -> skip
;
// Digits fragment
fragment DIGIT
: [0-9]
;

File diff suppressed because one or more lines are too long

View File

@@ -1,49 +0,0 @@
LPAREN=1
RPAREN=2
LBRACK=3
RBRACK=4
COMMA=5
EQUALS=6
NOT_EQUALS=7
NEQ=8
LT=9
LE=10
GT=11
GE=12
LIKE=13
NOT_LIKE=14
ILIKE=15
NOT_ILIKE=16
BETWEEN=17
NOT_BETWEEN=18
EXISTS=19
NOT_EXISTS=20
REGEXP=21
NOT_REGEXP=22
CONTAINS=23
NOT_CONTAINS=24
IN=25
NOT_IN=26
NOT=27
AND=28
OR=29
HAS=30
HASANY=31
HASALL=32
HASNONE=33
BOOL=34
NUMBER=35
QUOTED_TEXT=36
KEY=37
WS=38
'('=1
')'=2
'['=3
']'=4
','=5
'!='=7
'<>'=8
'<'=9
'<='=10
'>'=11
'>='=12

File diff suppressed because one or more lines are too long

View File

@@ -1,49 +0,0 @@
LPAREN=1
RPAREN=2
LBRACK=3
RBRACK=4
COMMA=5
EQUALS=6
NOT_EQUALS=7
NEQ=8
LT=9
LE=10
GT=11
GE=12
LIKE=13
NOT_LIKE=14
ILIKE=15
NOT_ILIKE=16
BETWEEN=17
NOT_BETWEEN=18
EXISTS=19
NOT_EXISTS=20
REGEXP=21
NOT_REGEXP=22
CONTAINS=23
NOT_CONTAINS=24
IN=25
NOT_IN=26
NOT=27
AND=28
OR=29
HAS=30
HASANY=31
HASALL=32
HASNONE=33
BOOL=34
NUMBER=35
QUOTED_TEXT=36
KEY=37
WS=38
'('=1
')'=2
'['=3
']'=4
','=5
'!='=7
'<>'=8
'<'=9
'<='=10
'>'=11
'>='=12

View File

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

View File

@@ -0,0 +1,29 @@
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import axios, { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { ChangelogSchema } from 'types/api/changelog/getChangelogByVersion';
const getChangelogByVersion = async (
versionId: string,
): Promise<SuccessResponse<ChangelogSchema> | ErrorResponse> => {
try {
const response = await axios.get(`
https://cms.signoz.cloud/api/release-changelogs?filters[version][$eq]=${versionId}&populate[features][sort]=sort_order:asc&populate[features][populate][media][fields]=id,ext,url,mime,alternativeText
`);
if (!Array.isArray(response.data.data) || response.data.data.length === 0) {
throw new Error('No changelog found!');
}
return {
statusCode: 200,
error: null,
message: response.statusText,
payload: response.data.data[0],
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getChangelogByVersion;

View File

@@ -1,18 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { GetAllOrgPreferencesResponseProps } from 'types/api/preferences/userOrgPreferences';
const getAllOrgPreferences = async (): Promise<
SuccessResponse<GetAllOrgPreferencesResponseProps> | ErrorResponse
> => {
const response = await axios.get(`/org/preferences`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
};
export default getAllOrgPreferences;

View File

@@ -1,18 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { GetAllUserPreferencesResponseProps } from 'types/api/preferences/userOrgPreferences';
const getAllUserPreferences = async (): Promise<
SuccessResponse<GetAllUserPreferencesResponseProps> | ErrorResponse
> => {
const response = await axios.get(`/user/preferences`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
};
export default getAllUserPreferences;

View File

@@ -1,20 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { GetOrgPreferenceResponseProps } from 'types/api/preferences/userOrgPreferences';
const getOrgPreference = async ({
preferenceID,
}: {
preferenceID: string;
}): Promise<SuccessResponse<GetOrgPreferenceResponseProps> | ErrorResponse> => {
const response = await axios.get(`/org/preferences/${preferenceID}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
};
export default getOrgPreference;

View File

@@ -1,22 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { GetUserPreferenceResponseProps } from 'types/api/preferences/userOrgPreferences';
const getUserPreference = async ({
preferenceID,
}: {
preferenceID: string;
}): Promise<
SuccessResponse<GetUserPreferenceResponseProps> | ErrorResponse
> => {
const response = await axios.get(`/user/preferences/${preferenceID}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
};
export default getUserPreference;

View File

@@ -1,28 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
UpdateOrgPreferenceProps,
UpdateOrgPreferenceResponseProps,
} from 'types/api/preferences/userOrgPreferences';
const updateOrgPreference = async (
preferencePayload: UpdateOrgPreferenceProps,
): Promise<
SuccessResponse<UpdateOrgPreferenceResponseProps> | ErrorResponse
> => {
const response = await axios.put(
`/org/preferences/${preferencePayload.preferenceID}`,
{
preference_value: preferencePayload.value,
},
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default updateOrgPreference;

View File

@@ -1,28 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
UpdateUserPreferenceProps,
UpdateUserPreferenceResponseProps,
} from 'types/api/preferences/userOrgPreferences';
const updateUserPreference = async (
preferencePayload: UpdateUserPreferenceProps,
): Promise<
SuccessResponse<UpdateUserPreferenceResponseProps> | ErrorResponse
> => {
const response = await axios.put(
`/user/preferences/${preferencePayload.preferenceID}`,
{
preference_value: preferencePayload.value,
},
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default updateUserPreference;

View File

@@ -8,4 +8,10 @@ import {
export const getKeySuggestions = (
props: QueryKeyRequestProps,
): Promise<AxiosResponse<QueryKeySuggestionsResponseProps>> =>
axios.get(`/fields/keys?signal=${props.signal}&name=${props.name}`);
axios.get(
`/fields/keys?signal=${props.signal}&searchText=${
props.searchText
}&metricName=${props.metricName ?? ''}&fieldContext=${
props.fieldContext ?? ''
}&fieldDataType=${props.fieldDataType ?? ''}`,
);

View File

@@ -119,6 +119,7 @@ export const updateFunnelSteps = async (
export interface ValidateFunnelPayload {
start_time: number;
end_time: number;
steps: FunnelStepData[];
}
export interface ValidateFunnelResponse {
@@ -132,12 +133,11 @@ export interface ValidateFunnelResponse {
}
export const validateFunnelSteps = async (
funnelId: string,
payload: ValidateFunnelPayload,
signal?: AbortSignal,
): Promise<SuccessResponse<ValidateFunnelResponse> | ErrorResponse> => {
const response = await axios.post(
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/validate`,
`${FUNNELS_BASE_PATH}/analytics/validate`,
payload,
{ signal },
);
@@ -185,6 +185,7 @@ export interface FunnelOverviewPayload {
end_time: number;
step_start?: number;
step_end?: number;
steps: FunnelStepData[];
}
export interface FunnelOverviewResponse {
@@ -196,20 +197,17 @@ export interface FunnelOverviewResponse {
avg_rate: number;
conversion_rate: number | null;
errors: number;
// TODO(shaheer): remove p99_latency once we have support for latency
p99_latency: number;
latency: number;
};
}>;
}
export const getFunnelOverview = async (
funnelId: string,
payload: FunnelOverviewPayload,
signal?: AbortSignal,
): Promise<SuccessResponse<FunnelOverviewResponse> | ErrorResponse> => {
const response = await axios.post(
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/overview`,
`${FUNNELS_BASE_PATH}/analytics/overview`,
payload,
{
signal,
@@ -237,12 +235,11 @@ export interface SlowTraceData {
}
export const getFunnelSlowTraces = async (
funnelId: string,
payload: FunnelOverviewPayload,
signal?: AbortSignal,
): Promise<SuccessResponse<SlowTraceData> | ErrorResponse> => {
const response = await axios.post(
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/slow-traces`,
`${FUNNELS_BASE_PATH}/analytics/slow-traces`,
payload,
{
signal,
@@ -275,7 +272,7 @@ export const getFunnelErrorTraces = async (
signal?: AbortSignal,
): Promise<SuccessResponse<ErrorTraceData> | ErrorResponse> => {
const response: AxiosResponse = await axios.post(
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/error-traces`,
`${FUNNELS_BASE_PATH}/analytics/error-traces`,
payload,
{
signal,
@@ -293,6 +290,7 @@ export const getFunnelErrorTraces = async (
export interface FunnelStepsPayload {
start_time: number;
end_time: number;
steps: FunnelStepData[];
}
export interface FunnelStepGraphMetrics {
@@ -309,12 +307,11 @@ export interface FunnelStepsResponse {
}
export const getFunnelSteps = async (
funnelId: string,
payload: FunnelStepsPayload,
signal?: AbortSignal,
): Promise<SuccessResponse<FunnelStepsResponse> | ErrorResponse> => {
const response = await axios.post(
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/steps`,
`${FUNNELS_BASE_PATH}/analytics/steps`,
payload,
{ signal },
);
@@ -332,6 +329,7 @@ export interface FunnelStepsOverviewPayload {
end_time: number;
step_start?: number;
step_end?: number;
steps: FunnelStepData[];
}
export interface FunnelStepsOverviewResponse {
@@ -343,12 +341,11 @@ export interface FunnelStepsOverviewResponse {
}
export const getFunnelStepsOverview = async (
funnelId: string,
payload: FunnelStepsOverviewPayload,
signal?: AbortSignal,
): Promise<SuccessResponse<FunnelStepsOverviewResponse> | ErrorResponse> => {
const response = await axios.post(
`${FUNNELS_BASE_PATH}/${funnelId}/analytics/steps/overview`,
`${FUNNELS_BASE_PATH}/analytics/steps/overview`,
payload,
{ signal },
);

View File

@@ -1,21 +0,0 @@
import axios from 'api';
import { Props } from 'types/api/userFeedback/sendResponse';
const sendFeedback = async (props: Props): Promise<number> => {
const response = await axios.post(
'/feedback',
{
email: props.email,
message: props.message,
},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
},
);
return response.status;
};
export default sendFeedback;

View File

@@ -0,0 +1,23 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps } from 'types/api/preferences/list';
import { OrgPreference } from 'types/api/preferences/preference';
const listPreference = async (): Promise<
SuccessResponseV2<OrgPreference[]>
> => {
try {
const response = await axios.get<PayloadProps>(`/org/preferences`);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default listPreference;

View File

@@ -0,0 +1,25 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/preferences/get';
import { OrgPreference } from 'types/api/preferences/preference';
const getPreference = async (
props: Props,
): Promise<SuccessResponseV2<OrgPreference>> => {
try {
const response = await axios.get<PayloadProps>(
`/org/preferences/${props.name}`,
);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default getPreference;

View File

@@ -0,0 +1,22 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { Props } from 'types/api/preferences/update';
const update = async (props: Props): Promise<SuccessResponseV2<null>> => {
try {
const response = await axios.put(`/org/preferences/${props.name}`, {
value: props.value,
});
return {
httpStatusCode: response.status,
data: null,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default update;

View File

@@ -1,24 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps } from 'types/api/user/getUserPreference';
const getPreference = async (): Promise<
SuccessResponse<PayloadProps> | ErrorResponse
> => {
try {
const response = await axios.get(`/user/preferences`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default getPreference;

View File

@@ -0,0 +1,21 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps } from 'types/api/preferences/list';
import { UserPreference } from 'types/api/preferences/preference';
const list = async (): Promise<SuccessResponseV2<UserPreference[]>> => {
try {
const response = await axios.get<PayloadProps>(`/user/preferences`);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default list;

View File

@@ -0,0 +1,25 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { PayloadProps, Props } from 'types/api/preferences/get';
import { UserPreference } from 'types/api/preferences/preference';
const get = async (
props: Props,
): Promise<SuccessResponseV2<UserPreference>> => {
try {
const response = await axios.get<PayloadProps>(
`/user/preferences/${props.name}`,
);
return {
httpStatusCode: response.status,
data: response.data.data,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default get;

View File

@@ -0,0 +1,22 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
import { Props } from 'types/api/preferences/update';
const update = async (props: Props): Promise<SuccessResponseV2<null>> => {
try {
const response = await axios.put(`/user/preferences/${props.name}`, {
value: props.value,
});
return {
httpStatusCode: response.status,
data: null,
};
} catch (error) {
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
}
};
export default update;

View File

@@ -41,174 +41,55 @@ function convertTimeSeriesData(
};
}
/**
* Helper function to collect columns from scalar data
*/
function collectColumnsFromScalarData(
scalarData: ScalarData[],
): { name: string; queryName: string; isValueColumn: boolean }[] {
const columnMap = new Map<
string,
{ name: string; queryName: string; isValueColumn: boolean }
>();
scalarData.forEach((scalar) => {
scalar.columns.forEach((col) => {
if (col.columnType === 'group') {
// For group columns, use the column name as-is
const key = `${col.name}_group`;
if (!columnMap.has(key)) {
columnMap.set(key, {
name: col.name,
queryName: '', // Group columns don't have query names
isValueColumn: false,
});
}
} else if (col.columnType === 'aggregation') {
// For aggregation columns, use the query name as the column name
const key = `${col.queryName}_aggregation`;
if (!columnMap.has(key)) {
columnMap.set(key, {
name: col.queryName, // Use query name as column name (A, B, etc.)
queryName: col.queryName,
isValueColumn: true,
});
}
}
});
});
return Array.from(columnMap.values()).sort((a, b) => {
if (a.isValueColumn !== b.isValueColumn) {
return a.isValueColumn ? 1 : -1;
}
return a.name.localeCompare(b.name);
});
}
/**
* Helper function to process scalar data rows with unified table structure
*/
function processScalarDataRows(
scalarData: ScalarData[],
): { data: Record<string, any> }[] {
// First, identify all group columns and all value columns
const allGroupColumns = new Set<string>();
const allValueColumns = new Set<string>();
scalarData.forEach((scalar) => {
scalar.columns.forEach((col) => {
if (col.columnType === 'group') {
allGroupColumns.add(col.name);
} else if (col.columnType === 'aggregation') {
// Use query name for value columns to match expected format
allValueColumns.add(col.queryName);
}
});
});
// Create a unified row structure
const unifiedRows = new Map<string, Record<string, any>>();
// Process each scalar result
scalarData.forEach((scalar) => {
scalar.data.forEach((dataRow) => {
const groupColumns = scalar.columns.filter(
(col) => col.columnType === 'group',
);
// Create row key based on group columns
let rowKey: string;
const groupValues: Record<string, any> = {};
if (groupColumns.length > 0) {
const keyParts: string[] = [];
groupColumns.forEach((col, index) => {
const value = dataRow[index];
keyParts.push(String(value));
groupValues[col.name] = value;
});
rowKey = keyParts.join('|');
} else {
// For scalar values without grouping, create a default row
rowKey = 'default_row';
// Set all group columns to 'n/a' for this row
Array.from(allGroupColumns).forEach((groupCol) => {
groupValues[groupCol] = 'n/a';
});
}
// Get or create the unified row
if (!unifiedRows.has(rowKey)) {
const newRow: Record<string, any> = { ...groupValues };
// Initialize all value columns to 'n/a'
Array.from(allValueColumns).forEach((valueCol) => {
newRow[valueCol] = 'n/a';
});
unifiedRows.set(rowKey, newRow);
}
const row = unifiedRows.get(rowKey)!;
// Fill in the aggregation values using query name as column name
scalar.columns.forEach((col, colIndex) => {
if (col.columnType === 'aggregation') {
row[col.queryName] = dataRow[colIndex];
}
});
});
});
return Array.from(unifiedRows.values()).map((rowData) => ({
data: rowData,
}));
}
/**
* Converts V5 ScalarData array to legacy format with table structure
*/
function convertScalarDataArrayToTable(
scalarDataArray: ScalarData[],
legendMap: Record<string, string>,
): QueryDataV3 {
): QueryDataV3[] {
// If no scalar data, return empty structure
if (!scalarDataArray || scalarDataArray.length === 0) {
return [];
}
// Process each scalar data separately to maintain query separation
return scalarDataArray?.map((scalarData) => {
// Get query name from the first column
const queryName = scalarData?.columns?.[0]?.queryName || '';
// Collect columns for this specific query
const columns = scalarData?.columns?.map((col) => ({
name: col.columnType === 'aggregation' ? col.queryName : col.name,
queryName: col.queryName,
isValueColumn: col.columnType === 'aggregation',
}));
// Process rows for this specific query
const rows = scalarData?.data?.map((dataRow) => {
const rowData: Record<string, any> = {};
scalarData?.columns?.forEach((col, colIndex) => {
const columnName =
col.columnType === 'aggregation' ? col.queryName : col.name;
rowData[columnName] = dataRow[colIndex];
});
return { data: rowData };
});
return {
queryName: '',
legend: '',
queryName,
legend: legendMap[queryName] || '',
series: null,
list: null,
table: {
columns: [],
rows: [],
columns,
rows,
},
};
}
// Collect columns and process rows
const columns = collectColumnsFromScalarData(scalarDataArray);
const rows = processScalarDataRows(scalarDataArray);
// Get the primary query name
const primaryQuery = scalarDataArray.find((s) =>
s.columns.some((c) => c.columnType === 'aggregation'),
);
const queryName =
primaryQuery?.columns.find((c) => c.columnType === 'aggregation')
?.queryName ||
scalarDataArray[0]?.columns[0]?.queryName ||
'';
return {
queryName,
legend: legendMap[queryName] || queryName,
series: null,
list: null,
table: {
columns,
rows,
},
};
});
}
/**
@@ -268,11 +149,11 @@ function convertV5DataByType(
}
case 'scalar': {
const scalarData = v5Data.data.results as ScalarData[];
// For scalar data, combine all results into a single table
const combinedTable = convertScalarDataArrayToTable(scalarData, legendMap);
// For scalar data, combine all results into separate table entries
const combinedTables = convertScalarDataArrayToTable(scalarData, legendMap);
return {
resultType: 'scalar',
result: [combinedTable],
result: combinedTables,
};
}
case 'raw': {
@@ -339,7 +220,7 @@ export function convertV5ResponseToLegacy(
// If metric names is an empty object
if (isEmpty(queryData.metric)) {
// If metrics list is empty && the user haven't defined a legend then add the legend equal to the name of the query.
if (!newQueryData.legend) {
if (newQueryData.legend === undefined || newQueryData.legend === null) {
newQueryData.legend = queryData.queryName;
}
// If name of the query and the legend if inserted is same then add the same to the metrics object.

View File

@@ -4,7 +4,6 @@ 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,
QueryFunctionProps,
@@ -25,6 +24,7 @@ import {
RequestType,
TelemetryFieldKey,
TraceAggregation,
VariableItem,
} from 'types/api/v5/queryRange';
import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder';
@@ -125,10 +125,12 @@ function createBaseSpec(
selectFields: isEmpty(queryData.selectColumns)
? undefined
: queryData.selectColumns?.map(
(column: BaseAutocompleteData): TelemetryFieldKey => ({
name: column.key,
fieldDataType: column?.dataType as FieldDataType,
fieldContext: column?.type as FieldContext,
(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,
}),
),
};
@@ -316,13 +318,12 @@ export const prepareQueryRangePayloadV5 = ({
variables = {},
start: startTime,
end: endTime,
formatForWeb,
}: GetQueryResultsProps): PrepareQueryRangePayloadV5Result => {
let legendMap: Record<string, string> = {};
const requestType = mapPanelTypeToRequestType(graphType);
let queries: QueryEnvelope[] = [];
console.log('query', query);
switch (query.queryType) {
case EQueryType.QUERY_BUILDER: {
const { queryData: data, queryFormulas } = query.builder;
@@ -380,7 +381,13 @@ export const prepareQueryRangePayloadV5 = ({
compositeQuery: {
queries,
},
variables,
formatOptions: {
formatTableResultForUI: !!formatForWeb,
},
variables: Object.entries(variables).reduce((acc, [key, value]) => {
acc[key] = { value };
return acc;
}, {} as Record<string, VariableItem>),
};
return { legendMap, queryPayload };

View File

@@ -0,0 +1,161 @@
.changelog-modal {
.ant-modal-content {
padding: unset;
background-color: var(--bg-ink-400, #121317);
.ant-modal-header {
margin-bottom: unset;
}
.ant-modal-footer {
margin-top: unset;
}
}
&-title {
display: flex;
align-items: center;
gap: 8px;
background-color: var(--bg-ink-400, #121317);
padding: 16px;
font-size: 14px;
line-height: 20px;
color: var(--text-vanilla-100, #fff);
border-bottom: 1px solid var(--bg-slate-500, #161922);
}
&-footer.scroll-available {
.scroll-btn-container {
display: block;
}
}
&-footer {
position: relative;
border: 1px solid var(--bg-slate-500, #161922);
padding: 12px;
display: flex;
align-items: center;
justify-content: space-between;
&-label {
color: var(--text-robin-400, #7190f9);
font-size: 14px;
line-height: 24px;
position: relative;
padding-left: 14px;
&::before {
content: '';
position: absolute;
top: 50%;
left: 0;
transform: translateY(-50%);
width: 6px;
height: 6px;
border-radius: 100%;
background-color: var(--bg-robin-500, #7190f9);
}
}
&-ctas {
display: flex;
& svg {
font-size: 14px;
}
}
.scroll-btn-container {
display: none;
position: absolute;
top: -40px;
left: 50%;
transform: translateX(-50%);
.scroll-btn {
all: unset;
padding: 4px 12px 4px 10px;
background-color: var(--bg-slate-400, #1d212d);
border-radius: 20px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
transition: background-color 0.1s;
&:hover {
background-color: var(--bg-slate-200, #2c3140);
}
&:active {
background-color: var(--bg-slate-600, #1c1f2a);
}
span {
font-size: 12px;
line-height: 18px;
color: var(--text-vanilla-400, #c0c1c3);
}
// add animation to the chevrons down icon
svg {
animation: pulse 1s infinite;
}
}
}
}
&-content {
max-height: calc(100vh - 300px);
overflow-y: auto;
padding: 16px;
border: 1px solid var(--bg-slate-500, #161922);
border-top-width: 0;
border-bottom-width: 0;
}
}
// pulse for the scroll for more icon
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.lightMode {
.changelog-modal {
.ant-modal-content {
background-color: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
}
&-title {
background: var(--bg-vanilla-100);
color: var(--bg-ink-500);
border-color: var(--bg-vanilla-300);
}
&-content {
border-color: var(--bg-vanilla-300);
}
&-footer {
border-color: var(--bg-vanilla-300);
.scroll-btn-container {
.scroll-btn {
background-color: var(--bg-vanilla-300);
span {
color: var(--text-ink-500);
}
}
}
}
}
}

View File

@@ -0,0 +1,131 @@
import './ChangelogModal.styles.scss';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Modal } from 'antd';
import cx from 'classnames';
import dayjs from 'dayjs';
import { ChevronsDown, ScrollText } from 'lucide-react';
import { useAppContext } from 'providers/App/App';
import { useCallback, useEffect, useRef, useState } from 'react';
import ChangelogRenderer from './components/ChangelogRenderer';
interface Props {
onClose: () => void;
}
function ChangelogModal({ onClose }: Props): JSX.Element {
const [hasScroll, setHasScroll] = useState(false);
const changelogContentSectionRef = useRef<HTMLDivElement>(null);
const { changelog } = useAppContext();
const formattedReleaseDate = dayjs(changelog?.release_date).format(
'MMMM D, YYYY',
);
const checkScroll = useCallback((): void => {
if (changelogContentSectionRef.current) {
const {
scrollHeight,
clientHeight,
scrollTop,
} = changelogContentSectionRef.current;
const isAtBottom = scrollHeight - clientHeight - scrollTop <= 8;
setHasScroll(scrollHeight > clientHeight + 24 && !isAtBottom); // 24px - buffer height to show show more
}
}, []);
useEffect(() => {
checkScroll();
const changelogContentSection = changelogContentSectionRef.current;
if (changelogContentSection) {
changelogContentSection.addEventListener('scroll', checkScroll);
}
return (): void => {
if (changelogContentSection) {
changelogContentSection.removeEventListener('scroll', checkScroll);
}
};
}, [checkScroll]);
const onClickUpdateWorkspace = (): void => {
window.open(
'https://github.com/SigNoz/signoz/releases',
'_blank',
'noopener,noreferrer',
);
};
const onClickScrollForMore = (): void => {
if (changelogContentSectionRef.current) {
changelogContentSectionRef.current.scrollTo({
top: changelogContentSectionRef.current.scrollTop + 600, // Scroll 600px from the current position
behavior: 'smooth',
});
}
};
return (
<Modal
className={cx('changelog-modal')}
title={
<div className="changelog-modal-title">
<ScrollText size={16} />
Whats New Changelog : {formattedReleaseDate}
</div>
}
width={820}
open
onCancel={onClose}
footer={
<div
className={cx('changelog-modal-footer', hasScroll && 'scroll-available')}
>
{changelog?.features && changelog.features.length > 0 && (
<span className="changelog-modal-footer-label">
{changelog.features.length} new&nbsp;
{changelog.features.length > 1 ? 'features' : 'feature'}
</span>
)}
<div className="changelog-modal-footer-ctas">
<Button type="default" icon={<CloseOutlined />} onClick={onClose}>
Skip for now
</Button>
<Button
type="primary"
icon={<CheckOutlined />}
onClick={onClickUpdateWorkspace}
>
Update my workspace
</Button>
</div>
{changelog && (
<div className="scroll-btn-container">
<button
data-testid="scroll-more-btn"
type="button"
className="scroll-btn"
onClick={onClickScrollForMore}
>
<ChevronsDown size={14} />
<span>Scroll for more</span>
</button>
</div>
)}
</div>
}
>
<div
className="changelog-modal-content"
data-testid="changelog-content"
ref={changelogContentSectionRef}
>
{changelog && <ChangelogRenderer changelog={changelog} />}
</div>
</Modal>
);
}
export default ChangelogModal;

View File

@@ -0,0 +1,79 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { fireEvent, render, screen } from '@testing-library/react';
import ChangelogModal from '../ChangelogModal';
const mockChangelog = {
release_date: '2025-06-10',
features: [
{
id: 1,
title: 'Feature 1',
description: 'Description for feature 1',
media: null,
},
],
bug_fixes: 'Bug fix details',
maintenance: 'Maintenance details',
};
// Mock react-markdown to just render children as plain text
jest.mock(
'react-markdown',
() =>
function ReactMarkdown({ children }: any) {
return <div>{children}</div>;
},
);
// mock useAppContext
jest.mock('providers/App/App', () => ({
useAppContext: jest.fn(() => ({ changelog: mockChangelog })),
}));
describe('ChangelogModal', () => {
it('renders modal with changelog data', () => {
render(<ChangelogModal onClose={jest.fn()} />);
expect(
screen.getByText('Whats New ⎯ Changelog : June 10, 2025'),
).toBeInTheDocument();
expect(screen.getByText('Feature 1')).toBeInTheDocument();
expect(screen.getByText('Description for feature 1')).toBeInTheDocument();
expect(screen.getByText('Bug fix details')).toBeInTheDocument();
expect(screen.getByText('Maintenance details')).toBeInTheDocument();
});
it('calls onClose when Skip for now is clicked', () => {
const onClose = jest.fn();
render(<ChangelogModal onClose={onClose} />);
fireEvent.click(screen.getByText('Skip for now'));
expect(onClose).toHaveBeenCalled();
});
it('opens migration docs when Update my workspace is clicked', () => {
window.open = jest.fn();
render(<ChangelogModal onClose={jest.fn()} />);
fireEvent.click(screen.getByText('Update my workspace'));
expect(window.open).toHaveBeenCalledWith(
'https://github.com/SigNoz/signoz/releases',
'_blank',
'noopener,noreferrer',
);
});
it('scrolls for more when Scroll for more is clicked', () => {
render(<ChangelogModal onClose={jest.fn()} />);
const scrollBtn = screen.getByTestId('scroll-more-btn');
const contentDiv = screen.getByTestId('changelog-content');
if (contentDiv) {
contentDiv.scrollTo = jest.fn();
}
fireEvent.click(scrollBtn);
if (contentDiv) {
expect(contentDiv.scrollTo).toHaveBeenCalled();
}
});
});

View File

@@ -0,0 +1,63 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/no-identical-functions */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { render, screen } from '@testing-library/react';
import ChangelogRenderer from '../components/ChangelogRenderer';
// Mock react-markdown to just render children as plain text
jest.mock(
'react-markdown',
() =>
function ReactMarkdown({ children }: any) {
return <div>{children}</div>;
},
);
const mockChangelog = {
id: 1,
documentId: 'changelog-doc-1',
version: '1.0.0',
createdAt: '2025-06-09T12:00:00Z',
release_date: '2025-06-10',
features: [
{
id: 1,
documentId: '1',
title: 'Feature 1',
description: 'Description for feature 1',
sort_order: 1,
createdAt: '',
updatedAt: '',
publishedAt: '',
deployment_type: 'All',
media: {
id: 1,
documentId: 'doc1',
ext: '.webp',
url: '/uploads/feature1.webp',
mime: 'image/webp',
alternativeText: null,
},
},
],
bug_fixes: 'Bug fix details',
updatedAt: '2025-06-09T12:00:00Z',
publishedAt: '2025-06-09T12:00:00Z',
maintenance: 'Maintenance details',
};
describe('ChangelogRenderer', () => {
it('renders release date', () => {
render(<ChangelogRenderer changelog={mockChangelog} />);
expect(screen.getByText('June 10, 2025')).toBeInTheDocument();
});
it('renders features, media, and description', () => {
render(<ChangelogRenderer changelog={mockChangelog} />);
expect(screen.getByText('Feature 1')).toBeInTheDocument();
expect(screen.getByAltText('Media')).toBeInTheDocument();
expect(screen.getByText('Description for feature 1')).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,136 @@
.changelog-renderer {
position: relative;
padding-left: 20px;
.changelog-release-date {
font-size: 14px;
line-height: 20px;
color: var(--text-vanilla-400, #c0c1c3);
}
&-list {
display: flex;
flex-direction: column;
gap: 28px;
}
&-line {
position: absolute;
left: 0;
top: 6px;
bottom: -30px;
width: 1px;
background-color: var(--bg-slate-400, #1d212d);
.inner-ball {
position: absolute;
left: 50%;
width: 6px;
height: 6px;
border-radius: 100%;
transform: translateX(-50%);
background-color: var(--bg-robin-500, #7190f9);
}
}
ul,
ol {
list-style: none;
display: flex;
flex-direction: column;
gap: 16px;
padding-left: 30px;
li {
position: relative;
&::before {
content: '';
position: absolute;
left: -10px;
top: 10px;
width: 20px;
height: 2px;
background-color: var(--bg-robin-500, #7190f9);
transform: translate(-100%, -50%);
}
}
}
li,
p {
font-size: 14px;
line-height: 20px;
color: var(--text-vanilla-400, #c0c1c3);
}
code {
padding: 2px 4px;
background-color: var(--bg-slate-500, #161922);
border-radius: 6px;
font-size: 95%;
vertical-align: middle;
border: 1px solid var(--bg-slate-600, #1c1f2a);
}
a {
color: var(--text-robin-500, #7190f9);
font-weight: 600;
text-decoration: underline;
&:hover {
text-decoration: none;
}
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-weight: 600;
color: var(--text-vanilla-100, #fff);
}
h1 {
font-size: 24px;
line-height: 32px;
}
h2 {
font-size: 20px;
line-height: 28px;
}
.changelog-media-image {
height: auto;
width: 100%;
overflow: hidden;
border-radius: 4px;
border: 1px solid var(--bg-slate-400, #1d212d);
}
}
.lightMode {
.changelog-renderer {
.changelog-release-date {
color: var(--text-ink-500);
}
&-line {
background-color: var(--bg-vanilla-300);
}
li,
p {
color: var(--text-ink-500);
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: var(--text-ink-500);
}
}
}

View File

@@ -0,0 +1,89 @@
import './ChangelogRenderer.styles.scss';
import dayjs from 'dayjs';
import ReactMarkdown from 'react-markdown';
import {
ChangelogSchema,
Media,
SupportedImageTypes,
SupportedVideoTypes,
} from 'types/api/changelog/getChangelogByVersion';
interface Props {
changelog: ChangelogSchema;
}
function renderMedia(media: Media): JSX.Element | null {
if (SupportedImageTypes.includes(media.ext)) {
return (
<img
src={media.url}
alt={media.alternativeText || 'Media'}
width={800}
height={450}
className="changelog-media-image"
/>
);
}
if (SupportedVideoTypes.includes(media.ext)) {
return (
<video
autoPlay
controls
controlsList="nodownload noplaybackrate"
loop
className="my-3 h-auto w-full rounded"
>
<source src={media.url} type={media.mime} />
<track kind="captions" src="" label="No captions available" default />
Your browser does not support the video tag.
</video>
);
}
return null;
}
function ChangelogRenderer({ changelog }: Props): JSX.Element {
const formattedReleaseDate = dayjs(changelog.release_date).format(
'MMMM D, YYYY',
);
return (
<div className="changelog-renderer">
<div className="changelog-renderer-line">
<div className="inner-ball" />
</div>
<span className="changelog-release-date">{formattedReleaseDate}</span>
{changelog.features && changelog.features.length > 0 && (
<div className="changelog-renderer-list flex flex-col gap-7">
{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>
);
}
export default ChangelogRenderer;

View File

@@ -1,5 +1,6 @@
import { render, screen } from '@testing-library/react';
import ROUTES from 'constants/routes';
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
import { DataSource } from 'types/common/queryBuilder';
@@ -52,11 +53,32 @@ jest.mock('hooks/saveViews/useDeleteView', () => ({
})),
}));
// Mock usePreferenceSync
jest.mock('providers/preferences/sync/usePreferenceSync', () => ({
usePreferenceSync: (): any => ({
preferences: {
columns: [],
formatting: {
maxLines: 2,
format: 'table',
fontSize: 'small',
version: 1,
},
},
loading: false,
error: null,
updateColumns: jest.fn(),
updateFormatting: jest.fn(),
}),
}));
describe('ExplorerCard', () => {
it('renders a card with a title and a description', () => {
render(
<MockQueryClientProvider>
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
<PreferenceContextProvider>
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
</PreferenceContextProvider>
</MockQueryClientProvider>,
);
expect(screen.queryByText('Query Builder')).not.toBeInTheDocument();
@@ -65,7 +87,9 @@ describe('ExplorerCard', () => {
it('renders a save view button', () => {
render(
<MockQueryClientProvider>
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
<PreferenceContextProvider>
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
</PreferenceContextProvider>
</MockQueryClientProvider>,
);
expect(screen.queryByText('Save view')).not.toBeInTheDocument();

View File

@@ -6,6 +6,7 @@ import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import isEqual from 'lodash-es/isEqual';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import {
DeleteViewHandlerProps,
@@ -106,7 +107,11 @@ export const isQueryUpdatedInView = ({
!isEqual(
options?.selectColumns,
extraData && JSON.parse(extraData)?.selectColumns,
)
) ||
(stagedQuery?.builder?.queryData?.[0]?.dataSource === DataSource.LOGS &&
(!isEqual(options?.format, extraData && JSON.parse(extraData)?.format) ||
!isEqual(options?.maxLines, extraData && JSON.parse(extraData)?.maxLines) ||
!isEqual(options?.fontSize, extraData && JSON.parse(extraData)?.fontSize)))
);
};

View File

@@ -74,6 +74,7 @@ const formatMap = {
'MM/dd HH:mm': DATE_TIME_FORMATS.SLASH_SHORT,
'MM/DD': DATE_TIME_FORMATS.DATE_SHORT,
'YY-MM': DATE_TIME_FORMATS.YEAR_MONTH,
'MMM d, yyyy, h:mm:ss aaaa': DATE_TIME_FORMATS.DASH_DATETIME,
YY: DATE_TIME_FORMATS.YEAR_SHORT,
};

View File

@@ -430,9 +430,13 @@ function HostMetricsDetails({
>
{host.active ? 'ACTIVE' : 'INACTIVE'}
</Tag>
<Tag className="infra-monitoring-tags" bordered>
{host.os}
</Tag>
{host.os ? (
<Tag className="infra-monitoring-tags" bordered>
{host.os}
</Tag>
) : (
<Typography.Text>-</Typography.Text>
)}
<div className="progress-container">
<Progress
percent={Number((host.cpu * 100).toFixed(1))}

View File

@@ -5,17 +5,19 @@ import cx from 'classnames';
import { OPERATORS } from 'constants/queryBuilder';
import { FontSize } from 'container/OptionsMenu/types';
import { memo, MouseEvent, ReactNode, useMemo } from 'react';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
function AddToQueryHOC({
fieldKey,
fieldValue,
onAddToQuery,
fontSize,
dataType = DataTypes.EMPTY,
children,
}: AddToQueryHOCProps): JSX.Element {
const handleQueryAdd = (event: MouseEvent<HTMLDivElement>): void => {
event.stopPropagation();
onAddToQuery(fieldKey, fieldValue, OPERATORS['=']);
onAddToQuery(fieldKey, fieldValue, OPERATORS['='], undefined, dataType);
};
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
@@ -35,9 +37,20 @@ function AddToQueryHOC({
export interface AddToQueryHOCProps {
fieldKey: string;
fieldValue: string;
onAddToQuery: (fieldKey: string, fieldValue: string, operator: string) => void;
onAddToQuery: (
fieldKey: string,
fieldValue: string,
operator: string,
isJSON?: boolean,
dataType?: DataTypes,
) => void;
fontSize: FontSize;
dataType?: DataTypes;
children: ReactNode;
}
AddToQueryHOC.defaultProps = {
dataType: DataTypes.EMPTY,
};
export default memo(AddToQueryHOC);

View File

@@ -20,6 +20,7 @@ export function getDefaultCellStyle(isDarkMode?: boolean): CSSProperties {
export const defaultTableStyle: CSSProperties = {
minWidth: '40rem',
maxWidth: '60rem',
};
export const defaultListViewPanelStyle: CSSProperties = {

View File

@@ -410,18 +410,18 @@ export default function LogsFormatOptionsMenu({
)}
<div className="column-format">
{addColumn?.value?.map(({ key, id }) => (
<div className="column-name" key={id}>
{addColumn?.value?.map(({ name }) => (
<div className="column-name" key={name}>
<div className="name">
<Tooltip placement="left" title={key}>
{key}
<Tooltip placement="left" title={name}>
{name}
</Tooltip>
</div>
{addColumn?.value?.length > 1 && (
<X
className="delete-btn"
size={14}
onClick={(): void => addColumn.onRemove(id as string)}
onClick={(): void => addColumn.onRemove(name)}
/>
)}
</div>

View File

@@ -7,9 +7,11 @@ import SpaceAggregationOptions from 'container/QueryBuilder/components/SpaceAggr
import { GroupByFilter, OperatorsSelect } from 'container/QueryBuilder/filters';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { Info } from 'lucide-react';
import { memo, useCallback, useMemo } from 'react';
import { memo, useCallback, useEffect, useMemo } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { useQueryBuilderV2Context } from '../../QueryBuilderV2Context';
const MetricsAggregateSection = memo(function MetricsAggregateSection({
query,
index,
@@ -21,6 +23,7 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
version: string;
panelType: PANEL_TYPES | null;
}): JSX.Element {
const { setAggregationOptions } = useQueryBuilderV2Context();
const {
operators,
spaceAggregationOptions,
@@ -33,6 +36,20 @@ const MetricsAggregateSection = memo(function MetricsAggregateSection({
entityVersion: version,
});
useEffect(() => {
setAggregationOptions([
{
func: query.spaceAggregation || 'count',
arg: query.aggregateAttribute.key || '',
},
]);
}, [
query.spaceAggregation,
query.aggregateAttribute.key,
setAggregationOptions,
query,
]);
const handleChangeGroupByKeys = useCallback(
(value: IBuilderQuery['groupBy']) => {
handleChangeQueryData('groupBy', value);

View File

@@ -53,22 +53,35 @@ const havingOperators = [
},
];
// Add common value suggestions
const commonValues = [
{ label: '0', value: '0 ' },
{ label: '1', value: '1 ' },
{ label: '5', value: '5 ' },
{ label: '10', value: '10 ' },
{ label: '50', value: '50 ' },
{ label: '100', value: '100 ' },
{ label: '1000', value: '1000 ' },
];
const conjunctions = [
{ label: 'AND', value: 'AND ' },
{ label: 'OR', value: 'OR ' },
];
// Custom extension to stop events from propagating to global shortcuts
const stopEventsExtension = EditorView.domEventHandlers({
keydown: (event) => {
// Stop all keyboard events from propagating to global shortcuts
event.stopPropagation();
event.stopImmediatePropagation();
return false; // Important for CM to know you handled it
},
input: (event) => {
event.stopPropagation();
return false;
},
focus: (event) => {
// Ensure focus events don't interfere with global shortcuts
event.stopPropagation();
return false;
},
blur: (event) => {
// Ensure blur events don't interfere with global shortcuts
event.stopPropagation();
return false;
},
});
function HavingFilter({
onClose,
onChange,
@@ -82,6 +95,11 @@ function HavingFilter({
const [input, setInput] = useState(
queryData?.havingExpression?.expression || '',
);
useEffect(() => {
setInput(queryData?.havingExpression?.expression || '');
}, [queryData?.havingExpression?.expression]);
const [isFocused, setIsFocused] = useState(false);
const editorRef = useRef<EditorView | null>(null);
@@ -221,22 +239,18 @@ function HavingFilter({
};
}
// Show value suggestions after operator
// Close dropdown after operator to allow custom value entry
if (isAfterOperator(tokens)) {
return {
from: context.pos,
options: [
...commonValues.map((value) => ({
...value,
apply: applyValueCompletion,
})),
{
label: 'Enter a custom number value',
type: 'text',
apply: applyValueCompletion,
},
],
};
return null;
}
// Hide suggestions while typing a value after an operator
if (
!text.endsWith(' ') &&
tokens.length >= 2 &&
havingOperators.some((op) => op.value === tokens[tokens.length - 2])
) {
return null;
}
// Suggest key/operator pairs and ( for grouping
@@ -316,6 +330,8 @@ function HavingFilter({
extensions={[
havingAutocomplete,
javascript({ jsx: false, typescript: false }),
stopEventsExtension,
EditorView.lineWrapping,
keymap.of([
...completionKeymap,
{

View File

@@ -48,10 +48,12 @@ function QueryAggregationOptions({
<div className="query-aggregation-interval-label">every</div>
<div className="query-aggregation-interval-input-container">
<InputWithLabel
initialValue={queryData.stepInterval ? queryData.stepInterval : '60'}
initialValue={
queryData.stepInterval ? queryData.stepInterval : undefined
}
className="query-aggregation-interval-input"
label="Seconds"
placeholder="60"
placeholder="Auto"
type="number"
onChange={handleAggregationIntervalChange}
labelAfter

View File

@@ -112,6 +112,30 @@ function getFunctionContextAtCursor(
return null;
}
// Custom extension to stop events from propagating to global shortcuts
const stopEventsExtension = EditorView.domEventHandlers({
keydown: (event) => {
// Stop all keyboard events from propagating to global shortcuts
event.stopPropagation();
event.stopImmediatePropagation();
return false; // Important for CM to know you handled it
},
input: (event) => {
event.stopPropagation();
return false;
},
focus: (event) => {
// Ensure focus events don't interfere with global shortcuts
event.stopPropagation();
return false;
},
blur: (event) => {
// Ensure blur events don't interfere with global shortcuts
event.stopPropagation();
return false;
},
});
// eslint-disable-next-line react/no-this-in-sfc
function QueryAggregationSelect({
onChange,
@@ -125,6 +149,13 @@ function QueryAggregationSelect({
const [input, setInput] = useState(
queryData?.aggregations?.map((i: any) => i.expression).join(' ') || '',
);
useEffect(() => {
setInput(
queryData?.aggregations?.map((i: any) => i.expression).join(' ') || '',
);
}, [queryData?.aggregations]);
const [cursorPos, setCursorPos] = useState(0);
const [functionArgPairs, setFunctionArgPairs] = useState<
{ func: string; arg: string }[]
@@ -457,6 +488,7 @@ function QueryAggregationSelect({
aggregatorAutocomplete,
javascript({ jsx: false, typescript: false }),
EditorView.lineWrapping,
stopEventsExtension,
keymap.of([
...completionKeymap,
{

View File

@@ -27,26 +27,57 @@ import {
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/types';
import { DataSource } from 'types/common/queryBuilder';
import { queryOperatorSuggestions, validateQuery } from 'utils/antlrQueryUtils';
import { getQueryContextAtCursor } from 'utils/queryContextUtils';
import {
negationQueryOperatorSuggestions,
queryOperatorSuggestions,
validateQuery,
} from 'utils/antlrQueryUtils';
import {
getQueryContextAtCursor,
getCurrentValueIndexAtCursor,
} from 'utils/queryContextUtils';
import { queryExamples } from './constants';
import { isNull } from 'lodash-es';
const { Panel } = Collapse;
// Custom extension to stop events
const stopEventsExtension = EditorView.domEventHandlers({
keydown: (event) => {
// Stop all keyboard events from propagating to global shortcuts
event.stopPropagation();
// Optionally: event.preventDefault();
event.stopImmediatePropagation();
return false; // Important for CM to know you handled it
},
input: (event) => {
event.stopPropagation();
return false;
},
focus: (event) => {
// Ensure focus events don't interfere with global shortcuts
event.stopPropagation();
return false;
},
blur: (event) => {
// Ensure blur events don't interfere with global shortcuts
event.stopPropagation();
return false;
},
});
// const disallowMultipleSpaces: Extension = EditorView.inputHandler.of(
// (view, from, to, text) => {
// const currentLine = view.state.doc.lineAt(from);
// const before = currentLine.text.slice(0, from - currentLine.from);
// const after = currentLine.text.slice(to - currentLine.from);
// const newText = before + text + after;
// return /\s{2,}/.test(newText);
// },
// );
function QuerySearch({
onChange,
queryData,
@@ -70,6 +101,10 @@ function QuerySearch({
errors: [],
});
useEffect(() => {
setQuery(queryData.filter?.expression || '');
}, [queryData.filter?.expression]);
const [keySuggestions, setKeySuggestions] = useState<
QueryKeyDataSuggestionsProps[] | null
>(null);
@@ -112,7 +147,8 @@ function QuerySearch({
const fetchKeySuggestions = async (searchText?: string): Promise<void> => {
const response = await getKeySuggestions({
signal: dataSource,
name: searchText || '',
searchText: searchText || '',
metricName: queryData.aggregateAttribute.key ?? undefined,
});
if (response.data.data) {
@@ -127,7 +163,7 @@ function QuerySearch({
setKeySuggestions([]);
fetchKeySuggestions();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataSource]);
}, [dataSource, queryData.aggregateAttribute.key]);
// Add a state for tracking editing mode
const [editingMode, setEditingMode] = useState<
@@ -165,6 +201,31 @@ function QuerySearch({
return op.toUpperCase() === 'IN' || op.toUpperCase() === 'NOT IN';
};
const formatValueForOperator = (
value: string,
operatorToken: string | undefined,
type: string,
): string => {
// If operator requires a list and value isn't already in list format
if (isListOperator(operatorToken) && !value.startsWith('[')) {
// For string values, wrap in quotes first, then in brackets
if (type === 'value' || type === 'keyword') {
const quotedValue = wrapStringValueInQuotes(value);
return `[${quotedValue}]`;
}
// For numbers, just wrap in brackets
return `[${value}]`;
}
// If we're already inside bracket list for IN operator and it's a string value
// just wrap in quotes but not brackets (we're already in brackets)
if (type === 'value' || type === 'keyword') {
return wrapStringValueInQuotes(value);
}
return value;
};
// Add cleanup effect to prevent component updates after unmount
useEffect(
(): (() => void) => (): void => {
@@ -438,10 +499,19 @@ function QuerySearch({
completion: any,
from: number,
to: number,
shouldAddSpace = true,
): void => {
view.dispatch({
changes: { from, to, insert: `${completion.apply} ` },
selection: { anchor: from + completion.apply.length + 1 },
changes: {
from,
to,
insert: shouldAddSpace ? `${completion.apply} ` : `${completion.apply}`,
},
selection: {
anchor:
from +
(shouldAddSpace ? completion.apply.length + 1 : completion.apply.length),
},
});
};
@@ -457,7 +527,49 @@ function QuerySearch({
from: number,
to: number,
): void => {
addSpaceAfterSelection(view, { apply: originalApply }, from, to);
let shouldDefaultApply = true;
// Changes to replace the value in-place with the existing value
const isValueType = queryContext.isInValue && option.type === 'value';
const pair = queryContext.currentPair;
if (isValueType) {
if (queryContext.isInBracketList && pair?.valuesPosition) {
const idx = getCurrentValueIndexAtCursor(
pair.valuesPosition,
cursorPos.ch,
);
if (!isNull(idx)) {
const { start, end } = pair.valuesPosition[idx];
if (typeof start === 'number' && typeof end === 'number') {
shouldDefaultApply = false;
addSpaceAfterSelection(
view,
{ apply: originalApply },
start,
end + 1,
false,
);
}
}
} else if (pair?.position) {
const { valueStart, valueEnd } = pair.position;
if (typeof valueStart === 'number' && typeof valueEnd === 'number') {
shouldDefaultApply = false;
addSpaceAfterSelection(
view,
{ apply: originalApply },
valueStart,
valueEnd + 1,
false,
);
}
}
}
if (shouldDefaultApply) {
addSpaceAfterSelection(view, { apply: originalApply }, from, to);
}
},
};
});
@@ -563,6 +675,10 @@ function QuerySearch({
// Get key information from context or current pair
const keyName = queryContext.keyToken || queryContext.currentPair?.key;
if (queryContext.currentPair?.hasNegation) {
options = negationQueryOperatorSuggestions;
}
// If we have a key context, add that info to the operator suggestions
if (keyName) {
// Find the key details from suggestions
@@ -645,7 +761,11 @@ function QuerySearch({
// Format values based on their type and the operator
if (option.type === 'value' || option.type === 'keyword') {
// String values get quoted
processedOption.apply = option.label;
processedOption.apply = formatValueForOperator(
option.label,
operatorName,
option.type,
);
// Add context info to the suggestion
if (keyName && operatorName) {
@@ -699,7 +819,6 @@ function QuerySearch({
{ label: 'HAS', type: 'function' },
{ label: 'HASANY', type: 'function' },
{ label: 'HASALL', type: 'function' },
{ label: 'HASNONE', type: 'function' },
];
// Add space after selection for functions
@@ -775,20 +894,26 @@ function QuerySearch({
}
}
// If no specific context is detected, provide general suggestions
options = [
...(keySuggestions || []),
{ label: 'AND', type: 'conjunction', boost: -10 },
{ label: 'OR', type: 'conjunction', boost: -10 },
{ label: '(', type: 'parenthesis', info: 'Open group', boost: -20 },
];
// // If no specific context is detected, provide general suggestions
// options = [
// ...(keySuggestions || []),
// { label: 'AND', type: 'conjunction', boost: -10 },
// { label: 'OR', type: 'conjunction', boost: -10 },
// { label: '(', type: 'parenthesis', info: 'Open group', boost: -20 },
// ];
// Add space after selection for general context
const optionsWithSpace = addSpaceToOptions(options);
// // Add space after selection for general context
// const optionsWithSpace = addSpaceToOptions(options);
// return {
// from: word?.from ?? 0,
// options: optionsWithSpace,
// };
// Don't show anything if no context detected
return {
from: word?.from ?? 0,
options: optionsWithSpace,
options: [],
};
}
@@ -992,8 +1117,8 @@ function QuerySearch({
</Collapse>
</Card>
)}
{/*
{queryContext && (
{/* {queryContext && (
<Card size="small" title="Current Context" className="query-context">
<div className="context-details">
<Space direction="vertical" size={4}>

View File

@@ -8,16 +8,11 @@ import SpanScopeSelector from 'container/QueryBuilder/filters/QueryBuilderSearch
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { Copy, Ellipsis, Trash } from 'lucide-react';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { memo, useCallback, useMemo, useState } from 'react';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { HandleChangeQueryDataV5 } from 'types/common/operations.types';
import { DataSource } from 'types/common/queryBuilder';
import {
convertAggregationToExpression,
convertFiltersToExpression,
convertHavingToExpression,
} from '../utils';
import MetricsAggregateSection from './MerticsAggregateSection/MetricsAggregateSection';
import { MetricsSelect } from './MetricsSelect/MetricsSelect';
import QueryAddOns from './QueryAddOns/QueryAddOns';
@@ -54,44 +49,6 @@ export const QueryV2 = memo(function QueryV2({
entityVersion: version,
});
// Convert old format to new format and update query when component mounts or query changes
const performQueryConversions = useCallback(() => {
// Convert filters if needed
if (query.filters?.items?.length > 0 && !query.filter?.expression) {
const convertedFilter = convertFiltersToExpression(query.filters);
handleChangeQueryData('filter', convertedFilter);
}
// Convert having if needed
if (query.having?.length > 0 && !query.havingExpression?.expression) {
const convertedHaving = convertHavingToExpression(query.having);
handleChangeQueryData('havingExpression', convertedHaving);
}
// Convert aggregation if needed
if (!query.aggregations && query.aggregateOperator) {
const convertedAggregation = convertAggregationToExpression(
query.aggregateOperator,
query.aggregateAttribute,
query.dataSource,
query.timeAggregation,
query.spaceAggregation,
) as any; // Type assertion to handle union type
handleChangeQueryData('aggregations', convertedAggregation);
}
}, [query, handleChangeQueryData]);
useEffect(() => {
const needsConversion =
(query.filters?.items?.length > 0 && !query.filter?.expression) ||
(query.having?.length > 0 && !query.havingExpression?.expression) ||
(!query.aggregations && query.aggregateOperator);
if (needsConversion) {
performQueryConversions();
}
}, [performQueryConversions, query]);
const handleToggleDisableQuery = useCallback(() => {
handleChangeQueryData('disabled', !query.disabled);
}, [handleChangeQueryData, query]);
@@ -208,67 +165,69 @@ export const QueryV2 = memo(function QueryV2({
</div>
)}
<div className="qb-elements-container">
<div className="qb-search-container">
{dataSource === DataSource.METRICS && (
<div className="metrics-select-container">
<MetricsSelect query={query} index={0} version="v4" />
</div>
)}
<div className="qb-search-filter-container">
<div className="query-search-container">
<QuerySearch
key={`query-search-${query.queryName}-${query.dataSource}`}
onChange={handleSearchChange}
queryData={query}
dataSource={dataSource}
/>
</div>
{showSpanScopeSelector && (
<div className="traces-search-filter-container">
<div className="traces-search-filter-in">in</div>
<SpanScopeSelector queryName={query.queryName} />
{!isCollapsed && (
<div className="qb-elements-container">
<div className="qb-search-container">
{dataSource === DataSource.METRICS && (
<div className="metrics-select-container">
<MetricsSelect query={query} index={index} version="v4" />
</div>
)}
</div>
</div>
{!showOnlyWhereClause &&
!isListViewPanel &&
dataSource !== DataSource.METRICS && (
<QueryAggregation
dataSource={dataSource}
key={`query-search-${query.queryName}-${query.dataSource}`}
panelType={panelType || undefined}
onAggregationIntervalChange={handleChangeAggregateEvery}
onChange={handleChangeAggregation}
queryData={query}
<div className="qb-search-filter-container">
<div className="query-search-container">
<QuerySearch
key={`query-search-${query.queryName}-${query.dataSource}`}
onChange={handleSearchChange}
queryData={query}
dataSource={dataSource}
/>
</div>
{showSpanScopeSelector && (
<div className="traces-search-filter-container">
<div className="traces-search-filter-in">in</div>
<SpanScopeSelector query={query} />
</div>
)}
</div>
</div>
{!showOnlyWhereClause &&
!isListViewPanel &&
dataSource !== DataSource.METRICS && (
<QueryAggregation
dataSource={dataSource}
key={`query-search-${query.queryName}-${query.dataSource}`}
panelType={panelType || undefined}
onAggregationIntervalChange={handleChangeAggregateEvery}
onChange={handleChangeAggregation}
queryData={query}
/>
)}
{!showOnlyWhereClause && dataSource === DataSource.METRICS && (
<MetricsAggregateSection
panelType={panelType}
query={query}
index={index}
key={`metrics-aggregate-section-${query.queryName}-${query.dataSource}`}
version="v4"
/>
)}
{!showOnlyWhereClause && dataSource === DataSource.METRICS && (
<MetricsAggregateSection
panelType={panelType}
query={query}
index={0}
key={`metrics-aggregate-section-${query.queryName}-${query.dataSource}`}
version="v4"
/>
)}
{!showOnlyWhereClause && (
<QueryAddOns
index={index}
query={query}
version="v3"
isListViewPanel={isListViewPanel}
showReduceTo={showReduceTo}
panelType={panelType}
/>
)}
</div>
{!showOnlyWhereClause && (
<QueryAddOns
index={index}
query={query}
version="v3"
isListViewPanel={isListViewPanel}
showReduceTo={showReduceTo}
panelType={panelType}
/>
)}
</div>
)}
</div>
</div>
);

View File

@@ -1,7 +1,12 @@
import { Tabs, TabsProps } from 'antd';
import { useLocation, useParams } from 'react-router-dom';
import { RouteTabProps } from './types';
interface Params {
[key: string]: string;
}
function RouteTab({
routes,
activeKey,
@@ -9,19 +14,38 @@ function RouteTab({
history,
...rest
}: RouteTabProps & TabsProps): JSX.Element {
const params = useParams<Params>();
const location = useLocation();
// Replace dynamic parameters in routes
const routesWithParams = routes.map((route) => ({
...route,
route: route.route.replace(
/:(\w+)/g,
(match, param) => params[param] || match,
),
}));
// Find the matching route for the current pathname
const currentRoute = routesWithParams.find((route) => {
const routePattern = route.route.replace(/:(\w+)/g, '([^/]+)');
const regex = new RegExp(`^${routePattern}$`);
return regex.test(location.pathname);
});
const onChange = (activeRoute: string): void => {
if (onChangeHandler) {
onChangeHandler(activeRoute);
}
const selectedRoute = routes.find((e) => e.key === activeRoute);
const selectedRoute = routesWithParams.find((e) => e.key === activeRoute);
if (selectedRoute) {
history.push(selectedRoute.route);
}
};
const items = routes.map(({ Component, name, route, key }) => ({
const items = routesWithParams.map(({ Component, name, route, key }) => ({
label: name,
key,
tabKey: route,
@@ -32,8 +56,8 @@ function RouteTab({
<Tabs
onChange={onChange}
destroyInactiveTabPane
activeKey={activeKey}
defaultActiveKey={activeKey}
activeKey={currentRoute?.key || activeKey}
defaultActiveKey={currentRoute?.key || activeKey}
animated
items={items}
// eslint-disable-next-line react/jsx-props-no-spreading

View File

@@ -30,5 +30,5 @@ export enum LOCALSTORAGE {
SHOW_EXCEPTIONS_QUICK_FILTERS = 'SHOW_EXCEPTIONS_QUICK_FILTERS',
BANNER_DISMISSED = 'BANNER_DISMISSED',
QUICK_FILTERS_SETTINGS_ANNOUNCEMENT = 'QUICK_FILTERS_SETTINGS_ANNOUNCEMENT',
UNEXECUTED_FUNNELS = 'UNEXECUTED_FUNNELS',
FUNNEL_STEPS = 'FUNNEL_STEPS',
}

View File

@@ -0,0 +1,18 @@
export const ORG_PREFERENCES = {
ORG_ONBOARDING: 'org_onboarding',
WELCOME_CHECKLIST_DO_LATER: 'welcome_checklist_do_later',
WELCOME_CHECKLIST_SEND_LOGS_SKIPPED: 'welcome_checklist_send_logs_skipped',
WELCOME_CHECKLIST_SEND_TRACES_SKIPPED: 'welcome_checklist_send_traces_skipped',
WELCOME_CHECKLIST_SETUP_ALERTS_SKIPPED:
'welcome_checklist_setup_alerts_skipped',
WELCOME_CHECKLIST_SETUP_SAVED_VIEW_SKIPPED:
'welcome_checklist_setup_saved_view_skipped',
WELCOME_CHECKLIST_SEND_INFRA_METRICS_SKIPPED:
'welcome_checklist_send_infra_metrics_skipped',
WELCOME_CHECKLIST_SETUP_DASHBOARDS_SKIPPED:
'welcome_checklist_setup_dashboards_skipped',
WELCOME_CHECKLIST_SETUP_WORKSPACE_SKIPPED:
'welcome_checklist_setup_workspace_skipped',
WELCOME_CHECKLIST_ADD_DATA_SOURCE_SKIPPED:
'welcome_checklist_add_data_source_skipped',
};

View File

@@ -169,6 +169,9 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
aggregateAttribute: initialAutocompleteData,
timeAggregation: MetricAggregateOperator.RATE,
spaceAggregation: MetricAggregateOperator.SUM,
filter: { expression: '' },
aggregations: [{ expression: 'count() ' }],
havingExpression: { expression: '' },
functions: [],
filters: { items: [], op: 'AND' },
expression: createNewBuilderItemName({

View File

@@ -6,10 +6,6 @@ import {
import { SelectOption } from 'types/common/select';
export const metricAggregateOperatorOptions: SelectOption<string, string>[] = [
{
value: MetricAggregateOperator.NOOP,
label: 'NOOP',
},
{
value: MetricAggregateOperator.COUNT,
label: 'Count',
@@ -130,10 +126,6 @@ export const metricAggregateOperatorOptions: SelectOption<string, string>[] = [
];
export const tracesAggregateOperatorOptions: SelectOption<string, string>[] = [
{
value: TracesAggregatorOperator.NOOP,
label: 'NOOP',
},
{
value: TracesAggregatorOperator.COUNT,
label: 'Count',
@@ -217,10 +209,6 @@ export const tracesAggregateOperatorOptions: SelectOption<string, string>[] = [
];
export const logsAggregateOperatorOptions: SelectOption<string, string>[] = [
{
value: LogsAggregatorOperator.NOOP,
label: 'NOOP',
},
{
value: LogsAggregatorOperator.COUNT,
label: 'Count',

View File

@@ -29,12 +29,12 @@ const ROUTES = {
ALERT_OVERVIEW: '/alerts/overview',
ALL_CHANNELS: '/settings/channels',
CHANNELS_NEW: '/settings/channels/new',
CHANNELS_EDIT: '/settings/channels/:id',
CHANNELS_EDIT: '/settings/channels/edit/:channelId',
ALL_ERROR: '/exceptions',
ERROR_DETAIL: '/error-detail',
VERSION: '/status',
MY_SETTINGS: '/my-settings',
SETTINGS: '/settings',
MY_SETTINGS: '/settings/my-settings',
ORG_SETTINGS: '/settings/org-settings',
CUSTOM_DOMAIN_SETTINGS: '/settings/custom-domain-settings',
API_KEYS: '/settings/api-keys',
@@ -52,7 +52,7 @@ const ROUTES = {
LIST_LICENSES: '/licenses',
LOGS_INDEX_FIELDS: '/logs-explorer/index-fields',
TRACE_EXPLORER: '/trace-explorer',
BILLING: '/billing',
BILLING: '/settings/billing',
SUPPORT: '/support',
LOGS_SAVE_VIEWS: '/logs/saved-views',
TRACES_SAVE_VIEWS: '/traces/saved-views',
@@ -60,10 +60,12 @@ const ROUTES = {
TRACES_FUNNELS_DETAIL: '/traces/funnels/:funnelId',
WORKSPACE_LOCKED: '/workspace-locked',
WORKSPACE_SUSPENDED: '/workspace-suspended',
SHORTCUTS: '/shortcuts',
SHORTCUTS: '/settings/shortcuts',
INTEGRATIONS: '/integrations',
MESSAGING_QUEUES_BASE: '/messaging-queues',
MESSAGING_QUEUES_KAFKA: '/messaging-queues/kafka',
MESSAGING_QUEUES_KAFKA_DETAIL: '/messaging-queues/kafka/detail',
INFRASTRUCTURE_MONITORING_BASE: '/infrastructure-monitoring',
INFRASTRUCTURE_MONITORING_HOSTS: '/infrastructure-monitoring/hosts',
INFRASTRUCTURE_MONITORING_KUBERNETES: '/infrastructure-monitoring/kubernetes',
MESSAGING_QUEUES_CELERY_TASK: '/messaging-queues/celery-task',
@@ -71,6 +73,7 @@ const ROUTES = {
METRICS_EXPLORER: '/metrics-explorer/summary',
METRICS_EXPLORER_EXPLORER: '/metrics-explorer/explorer',
METRICS_EXPLORER_VIEWS: '/metrics-explorer/views',
API_MONITORING_BASE: '/api-monitoring',
API_MONITORING: '/api-monitoring/explorer',
METRICS_EXPLORER_BASE: '/metrics-explorer',
WORKSPACE_ACCESS_RESTRICTED: '/workspace-access-restricted',

View File

@@ -0,0 +1,4 @@
export const USER_PREFERENCES = {
SIDENAV_PINNED: 'sidenav_pinned',
NAV_SHORTCUTS: 'nav_shortcuts',
};

View File

@@ -7,7 +7,7 @@ import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { useCallback, useState } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath } from 'react-router-dom';
import { Channels } from 'types/api/channels/getAll';
@@ -17,14 +17,13 @@ import Delete from './Delete';
function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
const { t } = useTranslation(['channels']);
const { notifications } = useNotifications();
const [channels, setChannels] = useState<Channels[]>(allChannels);
const { user } = useAppContext();
const [action] = useComponentPermission(['new_alert_action'], user.role);
const onClickEditHandler = useCallback((id: string) => {
history.replace(
history.push(
generatePath(ROUTES.CHANNELS_EDIT, {
id,
channelId: id,
}),
);
}, []);
@@ -56,14 +55,19 @@ function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
<Button onClick={(): void => onClickEditHandler(id)} type="link">
{t('column_channel_edit')}
</Button>
<Delete id={id} setChannels={setChannels} notifications={notifications} />
<Delete id={id} notifications={notifications} />
</>
),
});
}
return (
<ResizeTable columns={columns} dataSource={channels} rowKey="id" bordered />
<ResizeTable
columns={columns}
dataSource={allChannels}
rowKey="id"
bordered
/>
);
}

View File

@@ -0,0 +1,4 @@
.alert-channels-container {
width: 90%;
margin: 12px auto;
}

View File

@@ -1,14 +1,15 @@
import { Button } from 'antd';
import { NotificationInstance } from 'antd/es/notification/interface';
import deleteChannel from 'api/channels/delete';
import { Dispatch, SetStateAction, useState } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Channels } from 'types/api/channels/getAll';
import { useQueryClient } from 'react-query';
import APIError from 'types/api/error';
function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
function Delete({ notifications, id }: DeleteProps): JSX.Element {
const { t } = useTranslation(['channels']);
const [loading, setLoading] = useState(false);
const queryClient = useQueryClient();
const onClickHandler = async (): Promise<void> => {
try {
@@ -21,7 +22,8 @@ function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
message: 'Success',
description: t('channel_delete_success'),
});
setChannels((preChannels) => preChannels.filter((e) => e.id !== id));
// Invalidate and refetch
queryClient.invalidateQueries(['getChannels']);
setLoading(false);
} catch (error) {
notifications.error({
@@ -46,7 +48,6 @@ function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
interface DeleteProps {
notifications: NotificationInstance;
setChannels: Dispatch<SetStateAction<Channels[]>>;
id: string;
}

View File

@@ -120,14 +120,19 @@ describe('Create Alert Channel', () => {
expect(screen.getByText('button_test_channel')).toBeInTheDocument();
expect(screen.getByText('button_return')).toBeInTheDocument();
});
it('Should check if saving the form without filling the name displays "Something went wrong"', async () => {
it('Should check if saving the form without filling the name displays error notification', async () => {
const saveButton = screen.getByRole('button', {
name: 'button_save_channel',
});
fireEvent.click(saveButton);
await waitFor(() => expect(showErrorModal).toHaveBeenCalled());
await waitFor(() =>
expect(errorNotification).toHaveBeenCalledWith({
message: 'Error',
description: 'channel_name_required',
}),
);
});
it('Should check if clicking on Test button shows "An alert has been sent to this channel" success message if testing passes', async () => {
server.use(

View File

@@ -1,3 +1,5 @@
import './AllAlertChannels.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Tooltip, Typography } from 'antd';
import getAll from 'api/channels/getAll';
@@ -56,7 +58,7 @@ function AlertChannels(): JSX.Element {
}
return (
<>
<div className="alert-channels-container">
<ButtonContainer>
<Paragraph ellipsis type="secondary">
{t('sending_channels_note')}
@@ -87,7 +89,7 @@ function AlertChannels(): JSX.Element {
</ButtonContainer>
<AlertChannelsComponent allChannels={data?.data || []} />
</>
</div>
);
}

View File

@@ -54,19 +54,21 @@ function DomainList(): JSX.Element {
// initialise tab with default query.
useShareBuilderUrl({
...initialQueriesMap.traces,
builder: {
...initialQueriesMap.traces.builder,
queryData: [
{
...initialQueriesMap.traces.builder.queryData[0],
dataSource: DataSource.TRACES,
aggregateOperator: 'noop',
aggregateAttribute: {
...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute,
defaultValue: {
...initialQueriesMap.traces,
builder: {
...initialQueriesMap.traces.builder,
queryData: [
{
...initialQueriesMap.traces.builder.queryData[0],
dataSource: DataSource.TRACES,
aggregateOperator: 'noop',
aggregateAttribute: {
...initialQueriesMap.traces.builder.queryData[0].aggregateAttribute,
},
},
},
],
],
},
},
});

View File

@@ -1,8 +1,34 @@
.app-banner-container {
position: relative;
width: 100%;
}
.app-layout {
position: relative;
height: 100%;
width: 100%;
&.isWorkspaceRestricted {
height: calc(100% - 32px);
// same styles as its either trial expired or payment failed
&.isTrialExpired {
height: calc(100% - 64px);
}
&.isPaymentFailed {
height: calc(100% - 64px);
}
}
&.isTrialExpired {
height: calc(100% - 32px);
}
&.isPaymentFailed {
height: calc(100% - 32px);
}
.app-content {
width: calc(100% - 64px); // width of the sidebar
z-index: 0;
@@ -22,6 +48,12 @@
width: 100%;
}
}
&.side-nav-pinned {
.app-content {
width: calc(100% - 240px);
}
}
}
.chat-support-gateway {
@@ -157,3 +189,9 @@
text-underline-offset: 2px;
}
}
.workspace-restricted-banner,
.trial-expiry-banner,
.payment-failed-banner {
height: 32px;
}

View File

@@ -7,6 +7,7 @@ import * as Sentry from '@sentry/react';
import { Flex } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import getChangelogByVersion from 'api/changelog/getChangelogByVersion';
import logEvent from 'api/common/logEvent';
import manageCreditCardApi from 'api/v1/portal/create';
import getUserLatestVersion from 'api/v1/version/getLatestVersion';
@@ -18,6 +19,7 @@ import { Events } from 'constants/events';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import { USER_PREFERENCES } from 'constants/userPreferences';
import SideNav from 'container/SideNav';
import TopNav from 'container/TopNav';
import dayjs from 'dayjs';
@@ -27,7 +29,6 @@ import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { isNull } from 'lodash-es';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { INTEGRATION_TYPES } from 'pages/Integrations/utils';
import { useAppContext } from 'providers/App/App';
import {
ReactNode,
@@ -40,9 +41,10 @@ import {
import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
import { useMutation, useQueries } from 'react-query';
import { useDispatch } from 'react-redux';
import { matchPath, useLocation } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { Dispatch } from 'redux';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import {
UPDATE_CURRENT_ERROR,
@@ -50,15 +52,18 @@ import {
UPDATE_LATEST_VERSION,
UPDATE_LATEST_VERSION_ERROR,
} from 'types/actions/app';
import { SuccessResponseV2 } from 'types/api';
import { ErrorResponse, SuccessResponse, SuccessResponseV2 } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import { ChangelogSchema } from 'types/api/changelog/getChangelogByVersion';
import APIError from 'types/api/error';
import {
LicenseEvent,
LicensePlatform,
LicenseState,
} from 'types/api/licensesV3/getActive';
import AppReducer from 'types/reducer/app';
import { USER_ROLES } from 'types/roles';
import { checkVersionState } from 'utils/app';
import { eventEmitter } from 'utils/getEventEmitter';
import {
getFormattedDate,
@@ -80,6 +85,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
featureFlags,
isFetchingFeatureFlags,
featureFlagsFetchError,
userPreferences,
updateChangelog,
} = useAppContext();
const { notifications } = useNotifications();
@@ -91,6 +98,15 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const [showSlowApiWarning, setShowSlowApiWarning] = useState(false);
const [slowApiWarningShown, setSlowApiWarningShown] = useState(false);
const [shouldFetchChangelog, setShouldFetchChangelog] = useState<boolean>(
false,
);
const { currentVersion, latestVersion } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const isLatestVersion = checkVersionState(currentVersion, latestVersion);
const handleBillingOnSuccess = (
data: SuccessResponseV2<CheckoutSuccessPayloadProps>,
@@ -128,7 +144,11 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([
const [
getUserVersionResponse,
getUserLatestVersionResponse,
getChangelogByVersionResponse,
] = useQueries([
{
queryFn: getUserVersion,
queryKey: ['getUserVersion', user?.accessJwt],
@@ -139,6 +159,12 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
queryKey: ['getUserLatestVersion', user?.accessJwt],
enabled: isLoggedIn,
},
{
queryFn: (): Promise<SuccessResponse<ChangelogSchema> | ErrorResponse> =>
getChangelogByVersion(latestVersion),
queryKey: ['getChangelogByVersion', latestVersion],
enabled: isLoggedIn && !isCloudUserVal && shouldFetchChangelog,
},
]);
useEffect(() => {
@@ -241,6 +267,30 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
notifications,
]);
useEffect(() => {
if (!isLatestVersion) {
setShouldFetchChangelog(true);
}
}, [isLatestVersion]);
useEffect(() => {
if (
getChangelogByVersionResponse.isFetched &&
getChangelogByVersionResponse.isSuccess &&
getChangelogByVersionResponse.data &&
getChangelogByVersionResponse.data.payload
) {
updateChangelog(getChangelogByVersionResponse.data.payload);
}
}, [
updateChangelog,
getChangelogByVersionResponse.isFetched,
getChangelogByVersionResponse.isLoading,
getChangelogByVersionResponse.isError,
getChangelogByVersionResponse.data,
getChangelogByVersionResponse.isSuccess,
]);
const isToDisplayLayout = isLoggedIn;
const routeKey = useMemo(() => getRouteKey(pathname), [pathname]);
@@ -330,53 +380,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
});
}, [manageCreditCard]);
const isHome = (): boolean => routeKey === 'HOME';
const isLogsView = (): boolean =>
routeKey === 'LOGS' ||
routeKey === 'LOGS_EXPLORER' ||
routeKey === 'LOGS_PIPELINES' ||
routeKey === 'LOGS_SAVE_VIEWS';
const isApiMonitoringView = (): boolean => routeKey === 'API_MONITORING';
const isExceptionsView = (): boolean => routeKey === 'ALL_ERROR';
const isTracesView = (): boolean =>
routeKey === 'TRACES_EXPLORER' || routeKey === 'TRACES_SAVE_VIEWS';
const isMessagingQueues = (): boolean =>
routeKey === 'MESSAGING_QUEUES_KAFKA' ||
routeKey === 'MESSAGING_QUEUES_KAFKA_DETAIL' ||
routeKey === 'MESSAGING_QUEUES_CELERY_TASK' ||
routeKey === 'MESSAGING_QUEUES_OVERVIEW';
const isCloudIntegrationPage = (): boolean =>
routeKey === 'INTEGRATIONS' &&
new URLSearchParams(window.location.search).get('integration') ===
INTEGRATION_TYPES.AWS_INTEGRATION;
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
const isAlertHistory = (): boolean => routeKey === 'ALERT_HISTORY';
const isAlertOverview = (): boolean => routeKey === 'ALERT_OVERVIEW';
const isInfraMonitoring = (): boolean =>
routeKey === 'INFRASTRUCTURE_MONITORING_HOSTS' ||
routeKey === 'INFRASTRUCTURE_MONITORING_KUBERNETES';
const isTracesFunnels = (): boolean => routeKey === 'TRACES_FUNNELS';
const isTracesFunnelDetails = (): boolean =>
!!matchPath(pathname, ROUTES.TRACES_FUNNELS_DETAIL);
const isPathMatch = (regex: RegExp): boolean => regex.test(pathname);
const isDashboardView = (): boolean =>
isPathMatch(/^\/dashboard\/[a-zA-Z0-9_-]+$/);
const isDashboardWidgetView = (): boolean =>
isPathMatch(/^\/dashboard\/[a-zA-Z0-9_-]+\/new$/);
const isTraceDetailsView = (): boolean =>
isPathMatch(/^\/trace\/[a-zA-Z0-9]+(\?.*)?$/);
useEffect(() => {
if (isDarkMode) {
document.body.classList.remove('lightMode');
@@ -593,61 +596,84 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
</div>
);
const sideNavPinned = userPreferences?.find(
(preference) => preference.name === USER_PREFERENCES.SIDENAV_PINNED,
)?.value as boolean;
const SHOW_TRIAL_EXPIRY_BANNER =
showTrialExpiryBanner && !showPaymentFailedWarning;
const SHOW_WORKSPACE_RESTRICTED_BANNER = showWorkspaceRestricted;
const SHOW_PAYMENT_FAILED_BANNER =
!showTrialExpiryBanner && showPaymentFailedWarning;
return (
<Layout className={cx(isDarkMode ? 'darkMode dark' : 'lightMode')}>
<Helmet>
<title>{pageTitle}</title>
</Helmet>
{showTrialExpiryBanner && !showPaymentFailedWarning && (
<div className="trial-expiry-banner">
You are in free trial period. Your free trial will end on{' '}
<span>{getFormattedDate(trialInfo?.trialEnd || Date.now())}.</span>
{user.role === USER_ROLES.ADMIN ? (
<span>
{' '}
Please{' '}
<a className="upgrade-link" onClick={handleUpgrade}>
upgrade
</a>
to continue using SigNoz features.
</span>
) : (
'Please contact your administrator for upgrading to a paid plan.'
{isLoggedIn && (
<div className={cx('app-banner-container')}>
{SHOW_TRIAL_EXPIRY_BANNER && (
<div className="trial-expiry-banner">
You are in free trial period. Your free trial will end on{' '}
<span>{getFormattedDate(trialInfo?.trialEnd || Date.now())}.</span>
{user.role === USER_ROLES.ADMIN ? (
<span>
{' '}
Please{' '}
<a className="upgrade-link" onClick={handleUpgrade}>
upgrade
</a>
to continue using SigNoz features.
</span>
) : (
'Please contact your administrator for upgrading to a paid plan.'
)}
</div>
)}
</div>
)}
{showWorkspaceRestricted && renderWorkspaceRestrictedBanner()}
{SHOW_WORKSPACE_RESTRICTED_BANNER && renderWorkspaceRestrictedBanner()}
{!showTrialExpiryBanner && showPaymentFailedWarning && (
<div className="payment-failed-banner">
Your bill payment has failed. Your workspace will get suspended on{' '}
<span>
{getFormattedDateWithMinutes(
dayjs(activeLicense?.event_queue?.scheduled_at).unix() || Date.now(),
)}
.
</span>
{user.role === USER_ROLES.ADMIN ? (
<span>
{' '}
Please{' '}
<a className="upgrade-link" onClick={handleFailedPayment}>
pay the bill
</a>
to continue using SigNoz features.
</span>
) : (
' Please contact your administrator to pay the bill.'
{SHOW_PAYMENT_FAILED_BANNER && (
<div className="payment-failed-banner">
Your bill payment has failed. Your workspace will get suspended on{' '}
<span>
{getFormattedDateWithMinutes(
dayjs(activeLicense?.event_queue?.scheduled_at).unix() || Date.now(),
)}
.
</span>
{user.role === USER_ROLES.ADMIN ? (
<span>
{' '}
Please{' '}
<a className="upgrade-link" onClick={handleFailedPayment}>
pay the bill
</a>
to continue using SigNoz features.
</span>
) : (
' Please contact your administrator to pay the bill.'
)}
</div>
)}
</div>
)}
<Flex
className={cx('app-layout', isDarkMode ? 'darkMode dark' : 'lightMode')}
className={cx(
'app-layout',
isDarkMode ? 'darkMode dark' : 'lightMode',
sideNavPinned ? 'side-nav-pinned' : '',
SHOW_WORKSPACE_RESTRICTED_BANNER ? 'isWorkspaceRestricted' : '',
SHOW_TRIAL_EXPIRY_BANNER ? 'isTrialExpired' : '',
SHOW_PAYMENT_FAILED_BANNER ? 'isPaymentFailed' : '',
)}
>
{isToDisplayLayout && !renderFullScreen && <SideNav />}
{isToDisplayLayout && !renderFullScreen && (
<SideNav isPinned={sideNavPinned} />
)}
<div
className={cx('app-content', {
'full-screen-content': renderFullScreen,
@@ -657,32 +683,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<LayoutContent data-overlayscrollbars-initialize>
<OverlayScrollbar>
<ChildrenContainer
style={{
margin:
isHome() ||
isLogsView() ||
isTracesView() ||
isDashboardView() ||
isDashboardWidgetView() ||
isDashboardListView() ||
isAlertHistory() ||
isAlertOverview() ||
isMessagingQueues() ||
isCloudIntegrationPage() ||
isInfraMonitoring() ||
isApiMonitoringView() ||
isExceptionsView()
? 0
: '0 1rem',
...(isTraceDetailsView() ||
isTracesFunnels() ||
isTracesFunnelDetails()
? { margin: 0 }
: {}),
}}
>
<ChildrenContainer>
{isToDisplayLayout && !renderFullScreen && <TopNav />}
{children}
</ChildrenContainer>

View File

@@ -1,7 +1,8 @@
.billing-container {
margin-bottom: 40px;
padding-top: 36px;
width: 65%;
width: 90%;
margin: 0 auto;
.billing-summary {
margin: 24px 8px;

View File

@@ -14,7 +14,7 @@ function CloudIntegrationPage(): JSX.Element {
<HeroSection />
<RequestIntegrationBtn
type={IntegrationType.AWS_SERVICES}
message="Cannot find the AWS service you're looking for? Request more integrations"
message="Can't find the AWS service you're looking for? Request more integrations"
/>
<ServicesTabs />
</div>

View File

@@ -60,26 +60,30 @@ function CloudServiceDataCollected({
return (
<div className="cloud-service-data-collected">
<div className="cloud-service-data-collected__table">
<div className="cloud-service-data-collected__table-heading">Logs</div>
<Table
columns={logsColumns}
dataSource={logsData}
// eslint-disable-next-line react/jsx-props-no-spreading
{...tableProps}
className="cloud-service-data-collected__table-logs"
/>
</div>
<div className="cloud-service-data-collected__table">
<div className="cloud-service-data-collected__table-heading">Metrics</div>
<Table
columns={metricsColumns}
dataSource={metricsData}
// eslint-disable-next-line react/jsx-props-no-spreading
{...tableProps}
className="cloud-service-data-collected__table-metrics"
/>
</div>
{logsData && logsData.length > 0 && (
<div className="cloud-service-data-collected__table">
<div className="cloud-service-data-collected__table-heading">Logs</div>
<Table
columns={logsColumns}
dataSource={logsData}
// eslint-disable-next-line react/jsx-props-no-spreading
{...tableProps}
className="cloud-service-data-collected__table-logs"
/>
</div>
)}
{metricsData && metricsData.length > 0 && (
<div className="cloud-service-data-collected__table">
<div className="cloud-service-data-collected__table-heading">Metrics</div>
<Table
columns={metricsColumns}
dataSource={metricsData}
// eslint-disable-next-line react/jsx-props-no-spreading
{...tableProps}
className="cloud-service-data-collected__table-metrics"
/>
</div>
)}
</div>
);
}

View File

@@ -51,6 +51,33 @@ function ServiceStatus({
return <div className={`service-status ${className}`}>{text}</div>;
}
function getTabItems(serviceDetailsData: any): TabsProps['items'] {
const dashboards = serviceDetailsData?.assets.dashboards || [];
const dataCollected = serviceDetailsData?.data_collected || {};
const items: TabsProps['items'] = [];
if (dashboards.length) {
items.push({
key: 'dashboards',
label: `Dashboards (${dashboards.length})`,
children: <CloudServiceDashboards service={serviceDetailsData} />,
});
}
items.push({
key: 'data-collected',
label: 'Data Collected',
children: (
<CloudServiceDataCollected
logsData={dataCollected.logs || []}
metricsData={dataCollected.metrics || []}
/>
),
});
return items;
}
function ServiceDetails(): JSX.Element | null {
const urlQuery = useUrlQuery();
const cloudAccountId = urlQuery.get('cloudAccountId');
@@ -106,23 +133,7 @@ function ServiceDetails(): JSX.Element | null {
return null;
}
const tabItems: TabsProps['items'] = [
{
key: 'dashboards',
label: `Dashboards (${serviceDetailsData?.assets.dashboards.length})`,
children: <CloudServiceDashboards service={serviceDetailsData} />,
},
{
key: 'data-collected',
label: 'Data Collected',
children: (
<CloudServiceDataCollected
logsData={serviceDetailsData?.data_collected.logs || []}
metricsData={serviceDetailsData?.data_collected.metrics || []}
/>
),
},
];
const tabItems = getTabItems(serviceDetailsData);
return (
<div className="service-details">

View File

@@ -34,7 +34,7 @@ describe('Request AWS integration', () => {
expect(
screen.getByText(
/cannot find what youre looking for\? request more integrations/i,
/can't find what youre looking for\? request more integrations/i,
),
).toBeInTheDocument();

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