Compare commits

...

170 Commits

Author SHA1 Message Date
Srikanth Chekuri
a705e7320f chore: make step multiple of aggregation interval of table 2024-08-13 00:08:11 +05:30
Srikanth Chekuri
3297934a90 Merge branch 'develop' into agg-tables 2024-08-12 22:00:21 +05:30
Srikanth Chekuri
7811fdd17a fix: nil pointer dereference for empty payload (#5680) 2024-08-12 21:43:38 +05:30
Srikanth Chekuri
a56b32328d chore: update the step interval 2024-08-12 19:58:07 +05:30
Srikanth Chekuri
424c08805e chore: update query builder to use 5min/30min aggregation tables 2024-08-12 19:24:45 +05:30
Yunus M
0dca1237b9 feat: add beta tag for service map and light mode (#5674)
* feat: add beta tag for service map and light mode

* chore: update test case
2024-08-12 16:41:47 +05:30
Vikrant Gupta
f3d73f6d44 feat: use selected columns as pinned attributes (#5601)
* feat: use selected columns as pinned attributes

* chore: handle nested json structs

* chore: refactor and fix build issues

* feat: handle changes for dashboard list panel

* chore: remove console logs
2024-08-12 16:34:43 +05:30
Vikrant Gupta
187927403a fix: clean out the panel type change attribute dependency (#5648)
* fix: clean out the panel type change attribute dependency

* fix: clean out the updater function as well

* fix: issue with rendering list panel as first and then moving around
2024-08-11 16:46:18 +05:30
Srikanth Chekuri
0157b47424 chore: add empty labels to response (#5668) 2024-08-09 18:19:15 +05:30
Srikanth Chekuri
156905afc7 fix: send alert default annotations for missing data alert (#5315) 2024-08-09 15:31:39 +05:30
CheetoDa
a4878f6430 chore: updated k8s instructions (#5665)
Co-authored-by: Prashant Shahi <prashant@signoz.io>
2024-08-09 14:47:31 +05:30
Srikanth Chekuri
4489df6f39 feat: add runningDiff function (#5667) 2024-08-09 14:04:29 +05:30
Srikanth Chekuri
06c075466b chore: add eval tests for threshold rule (#5398) 2024-08-09 12:34:40 +05:30
Srikanth Chekuri
62be3e7c13 chore: enable caching for all panel types in metrics v4 (#5651) 2024-08-09 12:32:11 +05:30
Srikanth Chekuri
bb84960442 chore: add alerts state history query service impl (#5255) 2024-08-09 12:11:05 +05:30
Kobe Cai
52199361d5 chore: fix error message typo on update log field api (#5660) 2024-08-09 10:08:40 +05:30
Srikanth Chekuri
f031845300 chore: make eval delay configurable (#5649) 2024-08-08 17:34:25 +05:30
Shaheer Kochai
6f73bb6eca feat: login flow tests (#5540) 2024-08-08 09:18:23 +04:30
Shaheer Kochai
fe398bcc49 feat: my settings page tests (#5499)
* feat: my settings page tests

* chore: improve mysettings test names

* chore: remove commented code and console.log

* chore: add missing parentheses
2024-08-08 09:17:38 +04:30
Shaheer Kochai
6781c29082 feat: tests for alert channels settings (#5563)
* feat: tests for alert channels settings

* chore: overall improvements to alert channel settings tests

* chore: improve alerts dummy data
2024-08-08 08:52:15 +04:30
Raj Kamal Singh
eb146491f2 Feat: QS: query builder suggestions api v0 (#5634)
* chore: stash initial work with API signature

* chore: put together setup for integration testing filter suggestions

* feat: filter suggestions: suggest attribs using existing autocomplete logic

* chore: filter suggestions test: add expectation for example queries

* feat: filter suggestions: default suggestions when data yet to be received

* feat: finish plumbing basic example queries

* chore: add test for filter suggestions with an existing query

* feat: filter suggestions: don't suggest attribs already included in existing filter

* chore: generate example queries by including existing filter first

* chore: upgrade ClickHouse-go-mock

* chore: some cleanup of reader.GetQBFilterSuggestionsForLogs

* chore: some cleanup of filter suggestion tests

* chore: some cleanup to http handler and request parsing logic for filter suggestions

* chore: remove expectation that attrib suggestions won't contain attribs already used in filter
2024-08-08 09:27:41 +05:30
Vishal Sharma
ae325ec1ca chore: handle traceID search 404 performance issue (#5654)
By setting max and min timestamp filter same as current timestamp when traceIDs are not found
2024-08-08 08:32:11 +05:30
Srikanth Chekuri
fd6f0574f5 fix: make timeshift work with cache (#5646) 2024-08-06 20:24:06 +05:30
rahulkeswani101
b819a90c80 feat: added links to integrations page in onboarding section (#5606)
* feat: added links to integrations page in onboarding section

* feat: removed box shadow for button

* refactor: added routes object to navigate to integrations page

* feat: added new styles for data source name
2024-08-06 19:18:48 +05:30
rahulkeswani101
a6848f6abd fix: added card to show message for deleted alert id (#5565)
* fix: added card to show message for deleted alert id

* refactor: added new constants for handling error message when alert is deleted

* refactor: added error response to error message field

* refactor: removed console statement

* refactor: renamed the variables
2024-08-06 19:09:49 +05:30
Shivanshu Raj Shrivastava
abe65975c9 Merge pull request #5542 from shivanshuraj1333/api-kafka
messaging queue, consumer lag APIs
2024-08-06 17:58:01 +05:30
Shivanshu Raj Shrivastava
5cedd57aa2 Merge branch 'develop' into api-kafka 2024-08-06 16:30:30 +05:30
rahulkeswani101
80a7b9d16d feat: added link for dashboard name (#5544)
* feat: added link for dashboard name

* refactor: added getLink function to get the link of dashboard details page

* refactor: changed the color for dashboard name

* refactor: updated the classname for dashboard name

* fix: update css tokens and light mode design

---------

Co-authored-by: vikrantgupta25 <vikrant.thomso@gmail.com>
2024-08-06 13:33:51 +05:30
Shivanshu Raj Shrivastava
9f7b2542ec Merge branch 'develop' into api-kafka 2024-08-06 10:13:28 +05:30
Srikanth Chekuri
4a4c9f26a2 chore: add Reduce To for pie chart (#5629) 2024-08-05 20:53:52 +05:30
shivanshu
c957c0f757 chore: addressing review comments 2024-08-05 18:14:40 +05:30
shivanshu
3ff0aa4b4b chore: consumer group filtering 2024-08-05 18:09:58 +05:30
shivanshu
063c9adba6 chore: pr-reviews 2024-08-05 18:09:58 +05:30
shivanshu
5c3ce146fa chore: add queue type 2024-08-05 18:09:58 +05:30
shivanshu
481bb6e8b8 feat: add consumer and producer APIs 2024-08-05 18:09:58 +05:30
Yunus M
61e6316736 feat: add 1 month option in time range (#5639) 2024-08-05 16:57:24 +05:30
Vikrant Gupta
f9d1494657 feat: added support for units for formula columns in table panel type (#5638)
* feat: added support for formula columns units

* chore: add unit test cases for query and formula units
2024-08-05 16:54:45 +05:30
dependabot[bot]
0021b4d784 chore(deps): bump fast-loops from 1.1.3 to 1.1.4 in /frontend (#5465)
Bumps [fast-loops](https://github.com/robinweser/fast-loops) from 1.1.3 to 1.1.4.
- [Commits](https://github.com/robinweser/fast-loops/commits)

---
updated-dependencies:
- dependency-name: fast-loops
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 10:51:27 +05:30
Yunus M
a5d5800871 feat: enable pagination for service listing,key operations,explorer table and dashboard table (#5625) 2024-08-02 21:51:09 +05:30
Srikanth Chekuri
16dc90bbd1 chore(telemetry): add telemetry for metrics query type and count prom… (#5627) 2024-08-02 18:45:02 +05:30
Prashant Shahi
fff61379fe fix: mount root path in /hostfs for hostmetrics (#5534)
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-08-02 16:06:21 +05:30
Vikrant Gupta
08a415032c chore: added service name and time params for top level operations (#5552)
* chore: added service name and time params for top level operations

* fix: build issues

* chore: update the useTopLevelOpertions to send start and end time

* chore: added extra checks to not send the param when undefined

* chore: added extra checks to not send the param when undefined

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2024-08-01 14:17:00 +05:30
Raj Kamal Singh
3783ffdd4c feat: show log severity indicator based on severity number if it's available when severity text is unknown (#4971)
* feat: set log sev indicator based on severity number if severity text is unknown

* chore: some cleanup

* chore: some more cleanup

* chore: update log state indicator utils test

* chore: some more cleanup

* fix: priority to severity_number over severity_text and update tests

* fix: made the severity_text check case insensitive and added null checks

---------

Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com>
2024-08-01 12:06:29 +05:30
Vikrant Gupta
a8e4359d95 fix: logs context not working because of incorrect request data (#5595) 2024-08-01 11:29:05 +05:30
UnCool-0x
d9e94a4067 feat: windows onboarding in cloud (#5525)
* feat: windows onboarding in cloud

* fix: missed file instructions

* feat: assigned vars

* feat: windows onboarding minor changes

---------
2024-08-01 09:27:13 +05:30
rahulkeswani101
ae19eaa76a feat: redirect to original page after login (#5604) 2024-08-01 08:49:26 +05:30
SagarRajput-7
fff9954da2 Schedule maintainence release changes (#5585)
* feat: schedule maintenance feedback fixes

* feat: schedule maintenance feedback fixes

* feat: code refactor

* feat: code refactor

* feat: fixed incorrect payload values from start and endTime

* feat: sorted list by updatedAt

* feat: removed dependency on BE response prop - kind

* feat: fixed timezone switching and adding different timezones
2024-07-31 22:30:42 +05:30
Vikrant Gupta
220edd139a fix: do not send query_range api call on every keystroke (#5613) 2024-07-31 21:21:02 +05:30
Raj Kamal Singh
59121bd932 chore: nginx integration: add note about adjusting regex if using custom log format (#5615) 2024-07-31 17:52:51 +05:30
Vishal Sharma
aef935a817 feat: faster traceID based filtering (#5607)
* feat: faster traceID based filtering

* chore: add error log
2024-07-31 16:00:57 +05:30
Srikanth Chekuri
f300518d61 chore: add telemetry for channel types (#5602) 2024-07-31 15:15:19 +05:30
Yunus M
18b608a1d8 feat: update logEvent to silently handle errors (#5599) 2024-07-30 18:24:55 +05:30
Yunus M
738d62c9cf fix: show 0 as limit is user has set it to 0 (#5605) 2024-07-30 18:09:29 +05:30
Srikanth Chekuri
38e694cd36 chore: only fetch top level operation from the selected time window (#5404) 2024-07-30 02:02:50 +05:30
Vikrant Gupta
1281330c52 fix: disable the unlock dashboard btn for integration dashboards (#5573)
* fix: disable the unlock dashboard btn for integration dashboards

* chore: added test cases for the integration / non integration dashboards
2024-07-29 15:47:09 +05:30
Yunus M
7b7cca7db7 chore: remove commented code (#5445) 2024-07-29 11:45:03 +05:30
rahulkeswani101
3134e8c1cf feat: removed top nav from new alerts landing page (#5538)
* feat: removed top nav from new alerts landing page

* feat: added new function to check new alerts landing page

---------

Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com>
2024-07-29 11:42:55 +05:30
Vikrant Gupta
d00024b64a feat: preference framework qs changes (#5527)
* feat: query service changes base setup for preferences

* feat: added handlers for user and org preferences

* chore: added base for all user and all org preferences

* feat: added handlers for all user and all org preferences

* feat: register the preference routes and initDB in pkg/query-service

* feat: code refactor

* chore: too much fun code refactor

* chore: little little missing attributes

* fix: handle range queries better

* fix: handle range queries better

* chore: address review comments

* chore: use struct inheritance for the all preferences struct

* chore: address review comments

* chore: address review comments

* chore: correct preference routes

* chore: low hanging optimisations

* chore: address review comments

* chore: address review comments

* chore: added extra validations for the check in allowed values

* fix: better handling for the jwt claims

* fix: better handling for the jwt claims

* chore: move the error to preference apis

* chore: move the error to preference apis

* fix: move the 401 logic to the auth middleware
2024-07-29 09:51:18 +05:30
Prashant Shahi
4360cd0397 fix(saml): handle invalid email domain (#5580)
### Summary

Handle the scenario when email with domain is used for SSO Login which does not match authenticated domains.

Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-07-27 09:52:53 +05:30
Vishal Sharma
a688b6c60e Revert "fix(saml): handle invalid email domain (#5564)" (#5579)
This reverts commit ba7e6fcf23.
2024-07-27 08:47:44 +05:30
Vishal Sharma
522e73b48e chore: move facing issues button in dashboards and disable intercom ping (#5571)
* chore: move facing issues button in dashboards and disable intercom ping

* chore: review comment
2024-07-26 18:51:48 +05:30
Prashant Shahi
ba7e6fcf23 fix(saml): handle invalid email domain (#5564)
### Summary

Handle the scenario when email with domain is used for SSO Login which does not match authenticated domains.

Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-07-26 18:41:39 +05:30
Vibhu Pandey
eefccafa5b feat(gateway): remove feature flag (#5561) 2024-07-26 12:31:33 +05:30
Vikrant Gupta
05bd6d52f1 fix: relative time param from the url not respected (#5545)
* fix: relative time param from the url not respected

* chore: added code comments and the priorities of the params

* fix: added validity checks for the relativeTime in the url
2024-07-25 23:23:01 +05:30
Vikrant Gupta
d60daef171 fix: handle the super set query reset state when changing widgets (#5539) 2024-07-23 20:21:25 +05:30
Vikrant Gupta
d50530f58c fix: retain the step interval while creating alerts from the dashboard panel (#5455)
* fix: use the same step interval as in the dashboard query while creating alerts from panel

* chore: added extra safety checks

* chore: add test cases for the mapQueryDataFromAPI utils

* chore: added functions test cases as well

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2024-07-23 17:20:31 +05:30
Yunus M
6957bd71ca chore: move from trackEvent to logEvent (#5530)
* chore: move from trackEvent to logEvent

* feat: update test cases
2024-07-23 16:32:45 +05:30
Vikrant Gupta
ef8b50c19e chore: addition of jest test cases for dashboards panels (#5506)
* chore: added value panel wrapper jest tests

* chore: added column units and legends test for table panel wrapper
2024-07-22 21:10:48 +05:30
Nityananda Gohain
1585065fff fix: use proper indexes for full text search (#4787)
* fix: use proper indexes for full text search

* fix: tests updated

* feat: lower support only for body and not attributes

* fix: remove default tolower

* fix: add comment for json key split

* fix: remove ilike only for body searches

* fix: minor fixes

* fix: minor fixes
2024-07-22 17:46:35 +05:30
Yunus M
99c68ddbcd feat: add learn more urls to ingestion settings page (#5526)
* feat: add learn more urls to ingestion settings page

* feat: enable multi ingestion settins for editors, add basic test cases
2024-07-22 16:29:00 +05:30
Vikrant Gupta
b08e859426 fix: do not add select columns when the datasource is logs (#5515)
* fix: do not add select columns when the datasource is logs

* chore: added data test id
2024-07-22 13:43:47 +05:30
SagarRajput-7
89fd3e4f55 chore: added trace filter test cases (#5451)
* feat: added trace filter test cases

* feat: added trace filter test cases - initial render

* feat: added test cases - query sync, filter section behaviour etc

* feat: deleted mock-data files

* feat: added test cases of undefined filters and items

* feat: deleted tsconfig

* feat: added clear and rest btn test cases for traces filters

* feat: added collapse and uncollapse test for traces filters
2024-07-22 11:05:20 +05:30
Vibhu Pandey
a2492b0135 ci(github): change to beta (#5524)
* ci(github): change to beta

* Update testing-deployment.yaml

* ci(staging): bump to beta
2024-07-19 11:59:40 +05:30
Nityananda Gohain
eb8ca5a7ca fix: ignore offset if timestamp is selected in order by (#5520)
* fix: ignore offset if timestamp is selected in order by

* fix: tests updated
2024-07-18 18:03:39 +05:30
Pranay Prateek
80133240ca fix: update community link (#5516)
* update community link

* Update copyright year
2024-07-18 17:25:32 +05:30
Vikrant Gupta
7d7d112f40 fix: the dashboard locked bar should be sticky at the bottom (#5512) 2024-07-18 13:56:18 +05:30
SagarRajput-7
add2d19614 fix: fixed logEvent breaking page due to lack of null checks (#5511)
* fix: fixed logEvent breaking page due to lack of null checks

* fix: fixed logEvent breaking page due to lack of null checks
2024-07-18 13:54:05 +05:30
Vikrant Gupta
adfe20e88a fix: url params should not propagate across pages (#5417)
* fix: dashboards list url query params isolation

* feat: order query param old logs explorer isolation

* feat: added extra checks in place

* fix: refactor the dashboards list page for better performance

* chore: add test cases for the dashboards list page

* fix: added test cases for dashboards list page

* fix: added code comments

* fix: added empty state for dashboards and no search state
2024-07-18 12:25:31 +05:30
Vishal Sharma
d3b83f5a41 chore: update heartbeat interval logic (#5507)
* chore: update heartbeat interval logic

* chore: address review comment
2024-07-17 17:05:15 +05:30
B Kevin Anderson
77eba9a558 fix: list panel not querying selected columns (#5452)
Added log and traces columns to query for list panels
  Closes #5064

Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com>
2024-07-17 12:19:00 +05:30
Vikrant Gupta
43e73e06fe chore: helpers required for dashboards e2e test cases (#5496)
* chore: helpers required for dashboards e2e test cases

* chore: helpers required for dashboards e2e test cases

* chore: helpers required for dashboards e2e test cases
2024-07-16 18:35:59 +05:30
Vikrant Gupta
840d8b2e49 fix: 404 not found when intercepting the ingestion key calls (#5490) 2024-07-16 14:30:03 +05:30
Vishal Sharma
df751c7f38 chore: add activation events (#5474) 2024-07-16 14:18:59 +05:30
Shaheer Kochai
cd07c743b6 Implement OverlayScrollbars throughout the app for MacOS-like scrolling experience (#5423)
* feat: build overlay scrollbar component for Virtuoso elements

* feat: apply overlay scroll to Virtuoso components

* feat: build overlay scrollbar component for normal scrollable sections

* feat: apply overlay scrollbar to normal scrollable sections

* feat: add dark mode UI support to overlay scrollbars

* chore: rename OverlayScrollbar to OverlayScrollbarForTypicalChildren

* chore: move inline style to scss file

* chore: rename VirtuosoOverlayScrollbar to OverlayScrollbarForVirtuosoChildren

* chore: move OverlayScrollbarForTypicalChildren to components folder

* chore: create a common component for handling Virtuoso and Typical scroll sections

* chore: rename Virtuoso and Typical Overlay Scrollbar components

* fix: fix the overlay scrollbar initialization flickering

* fix: remove calculated height from typical overlay scrollbar + remove the explicit height: 100%
2024-07-16 14:16:13 +05:30
Shaheer Kochai
46e6c34e51 fix: block alert creation if query_range API fails (#5441) 2024-07-16 14:13:25 +05:30
Yunus M
42f7905b3b feat: show status message, status code string, span kind in trace det… (#5428)
* feat: show status message, status code string, span kind in trace details

* chore: update tests

* chore: update snapshots
2024-07-16 11:00:29 +05:30
Vikrant Gupta
a6e68c6519 fix: issue with table sorting when column contains both string and numbers (#5458) 2024-07-15 21:15:37 +05:30
Vikrant Gupta
c7e3e6dc4e fix: retain legends while changing panel types (#5447) 2024-07-15 21:04:49 +05:30
Srikanth Chekuri
9194ab08b6 fix: incorrect response for promql value type panels (#5497) 2024-07-15 18:06:39 +05:30
Srikanth Chekuri
3ecb2e35ef chore: use version v4 for export panel from explorer pages (#5438) 2024-07-12 18:49:24 +05:30
SagarRajput-7
9844dcdfb7 fix: added logic to keep sections uncollapsed for all filtered items (#5371) 2024-07-10 12:43:39 +05:30
SagarRajput-7
ddf5569ce9 fix: added null check on filters obj (#5419)
* fix: added null check on filters obj

* feat: added test cases of undefined filters and items

* feat: added comments
2024-07-10 11:56:11 +05:30
Nityananda Gohain
83455e614e fix: disable removing a selected field (#5457)
* fix: disable removing a selected field

* fix: comment updated with issue link

* fix: remove local db
2024-07-10 11:23:29 +05:30
Srikanth Chekuri
831de18464 fix: concurrent map writes to temporalityMap (#5432) 2024-07-10 11:00:28 +05:30
dependabot[bot]
3b2a811f7b chore(deps): bump google.golang.org/grpc from 1.64.0 to 1.64.1 (#5463)
Bumps [google.golang.org/grpc](https://github.com/grpc/grpc-go) from 1.64.0 to 1.64.1.
- [Release notes](https://github.com/grpc/grpc-go/releases)
- [Commits](https://github.com/grpc/grpc-go/compare/v1.64.0...v1.64.1)

---
updated-dependencies:
- dependency-name: google.golang.org/grpc
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 08:11:53 +05:30
Yunus M
2c7a5126fd update project maintainers (#5460) 2024-07-10 00:30:25 +05:30
Shaheer Kochai
87f1597d4e fix: prevent overwriting query expression and queryName on switching between panel types (#5430) 2024-07-09 08:13:35 +04:30
Shaheer Kochai
916663b4d5 fix: fix the explorer toolbar buttons padding (#5443) 2024-07-09 08:12:25 +04:30
Shaheer Kochai
b0e355eb64 fix: properly render \n and \t in log details + apply Geist Mono font to the logs (#5347)
* fix: properly render newline and tab in log details

* fix: change font family and add tab size to properly render \t

* feat: apply Geist Mono font to the logs
2024-07-09 08:11:46 +04:30
Shaheer Kochai
69a39531f0 Merge pull request #5440 from SigNoz/feat--add-react-query-dev-tools-in-dev-env
chore: add react-query devtools in development env
2024-07-08 20:01:21 +04:30
SagarRajput-7
9c9ed741b2 feat: changed name from 'Histogram' to 'Frequency chart' (#5369)
* feat: changed name from 'Histogram' to 'Frequence chart'

* feat: cdoe refactor and test case changes

* feat: added test case for frequency chart
2024-07-08 20:02:10 +05:30
SagarRajput-7
e6eaaa660a feat: added invite team member from onboarding flow (#5410)
* feat: added invite team member from onboarding flow

* feat: removed commented code and added text to strings-translations

* feat: added en-gb strings

* feat: added more text to strings

* feat: removed commented code and app.ts changes

* feat: added test case for onboarding and invite flow

* feat: added invite team member logEvents

* feat: resovled comments

* feat: cdoe refactor and test case changes
2024-07-08 19:50:29 +05:30
Vikrant Gupta
79eef5bb91 fix: clickhouse editor cursor sync issue (#5435) 2024-07-08 19:27:02 +05:30
Vikrant Gupta
4d64f1dede chore: better logging for duplicate keyboard shortcuts (#5425)
* chore: better logging for duplicate keyboard shortcuts

* chore: skip flaky test

* fix: make the shortcut error silent in prod
2024-07-08 19:25:50 +05:30
Vikrant Gupta
bf177882e6 fix: resize observer charts issue in alerts builder (#5436) 2024-07-08 19:24:05 +05:30
SagarRajput-7
f6b29999c9 fix: added right margin to facing issues btn on dashboad detail page (#5365)
* fix: added right padding to facing issues btn on dashboad detail page

* fix: added right margin instead of padding
2024-07-08 19:17:27 +05:30
Shaheer Kochai
75815897b0 Merge branch 'develop' into feat--add-react-query-dev-tools-in-dev-env 2024-07-08 10:53:14 +04:30
SagarRajput-7
c9309eecaa feat: added empty states for list, trace and timeSeried view in traces (#5290)
* feat: added empty states for list, trace and timeSeried view in traces

* feat: test case skip

* feat: fixed import order

* feat: added utm parameter link

* feat: added strings

* feat: resovled comments

* feat: added common doclinks util

* feat: test case updated:
2024-07-08 11:19:07 +05:30
ahmadshaheer1
4264fc0f3a feat: add react-query devtools in development env 2024-07-07 10:46:49 +04:30
Prashant Shahi
ef854910db Merge pull request #5437 from SigNoz/sync/signoz-0.49.1
Sync/signoz 0.49.1
2024-07-05 19:56:34 +05:30
Prashant Shahi
6b8b2ae761 Merge pull request #5429 from SigNoz/release/v0.49.x
Release/v0.49.1
2024-07-04 22:37:04 +05:30
Prashant Shahi
a48340a2ea Merge branch 'main' into release/v0.49.x 2024-07-04 22:27:41 +05:30
Prashant Shahi
e542d2ee09 chore(signoz): 📌 pin versions: SigNoz 0.49.1, SigNoz OtelCollector 0.102.2
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-07-04 22:24:57 +05:30
Prashant Shahi
08431131a9 Merge branch 'develop' into release/v0.49.x 2024-07-04 22:21:07 +05:30
Nityananda Gohain
1b0ec8ac43 fix: typecase support added for float to int (#5408) 2024-07-04 12:08:42 +05:30
Yunus M
2e0ddc7c7f chore: remove dynamic config invocation (#5416) 2024-07-04 01:07:55 +05:30
Prashant Shahi
858a0cb0de Merge pull request #5418 from SigNoz/release/v0.49.x
Release/v0.49.x
2024-07-03 18:54:14 +05:30
Prashant Shahi
216ad36234 chore(signoz): 📌 pin versions: SigNoz 0.49.0, SigNoz OtelCollector 0.102.1
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-07-03 16:53:11 +05:30
Prashant Shahi
6628abd435 Merge branch 'main' into release/v0.49.x 2024-07-03 16:51:16 +05:30
dependabot[bot]
7c81270ed9 chore(deps): bump ws from 7.5.9 to 7.5.10 in /frontend (#5265)
Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 15:39:28 +05:30
dependabot[bot]
81c3e6fa65 chore(deps): bump braces from 3.0.2 to 3.0.3 in /frontend (#5196)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 12:33:22 +05:30
Yunus M
d215ce09b0 fix: remove pagination from members listing in org settings page (#5400) 2024-07-03 10:30:41 +05:30
Raj Kamal Singh
161a69fbe9 chore: remove workaround for supporting pipeline filters using attribs with . replaced with _ (#5405) 2024-07-02 17:14:08 +05:30
Srikanth Chekuri
3ee51770fd chore: remove rules dependency in CH reader (#5396) 2024-07-02 12:03:01 +05:30
Nityananda Gohain
932b7ddc69 fix: orderby validation and correction in logs old QB (#5399) 2024-07-02 11:53:30 +05:30
Vishal Sharma
6e466df89d chore: update posthog-js (#5382) 2024-07-01 21:11:31 +05:30
Srikanth Chekuri
326dec21fd fix: use the correct formatter for the description (#5388) 2024-07-01 18:34:02 +05:30
Srikanth Chekuri
b0b69c83db fix: use fill gaps only for time series panel types (#5387) 2024-07-01 14:06:28 +05:30
Vikrant Gupta
02106277a6 fix: restructure code to handle loading state for panel type change (#5378)
* fix: restructure code to handle loading state for panel type change

* fix: add inline comments
2024-06-28 13:53:35 +05:30
Vikrant Gupta
b34509215e fix: pie chart panels not rendering (#5376)
* fix: pie chart panels not rendering

* fix: restructure code
2024-06-28 12:10:57 +05:30
Raj Kamal Singh
fd603b8fdf Fix: pipeline alias collisions shouldnt lead to duplicate log processors (#5372)
* chore: add test validating pipeline alias collisions dont lead to bad config recommendations

* chore: emit error log on detecting duplicate processors in generated config

* chore: ensure collector config processor names for pipelines are unique

* chore: minor cleanups
2024-06-28 09:31:21 +05:30
Vikrant Gupta
c5d23336a7 chore: move the table calculation to backend (#5351) 2024-06-27 22:04:14 +05:30
SagarRajput-7
53c6288025 feat: added track event in Alerts - (multiple places) (#5354)
* feat: added track event in Alerts - (multiple places)

* feat: comment resolve and code refactor

* feat: add Alert Channel: Channel list page visited event

* feat: removed testSuccess variable and used responseStatus directly

* feat: added save status in alert channel: save action

* feat: added channel detail in save and test notification event

* feat: code refactor

* feat: added status message for save and test

* feat: added status message for save channel events

* feat: code refactor
2024-06-27 21:40:11 +05:30
Nityananda Gohain
4f2c314f39 feat: add spanKind and status in span response (#5120)
* feat: add spanKind and errorMessage in span response

* fix: add statusCodeString
2024-06-27 12:34:23 +05:30
Shaheer Kochai
1ad61615c6 Merge pull request #5362 from SigNoz/fix-duplicate-severityText-in-raw-logs
fix: remove duplicate severityText in raw logs
2024-06-27 10:31:49 +04:30
ahmadshaheer1
7ddfadfb18 fix: remove duplicate severityText in raw logs 2024-06-27 09:18:39 +04:30
KJ
a7e02af8b0 Hot rod load command fix (#5352)
* fix: added user_count and spawn_rate options to hotRod load data command

* fix: removed locust_count and hatch_rate options

* fix: updated user_count and spawn_rate values to the default values used in other places

---------

Co-authored-by: Prashant Shahi <prashant@signoz.io>
2024-06-27 00:07:34 +05:30
Vikrant Gupta
da3f6fd7fd fix: labelsArray being returned null despite of labels being present (#5357) 2024-06-26 22:09:14 +05:30
Shaheer Kochai
a453471b51 Merge pull request #5316 from SigNoz/remove-pagination-for-single-page-lists-
feat: remove pagination for single-page lists
2024-06-26 19:54:11 +04:30
ahmadshaheer1
13df87ed69 chore: discard passing 'hideOnSinglePage' to <ResizeTable, since already overridden 2024-06-26 18:44:27 +05:30
ahmadshaheer1
f23ceea54e chore: remove the unnecessary hideOnSinglePage prop 2024-06-26 18:44:27 +05:30
ahmadshaheer1
46b4c8a004 chore: extract pagination config 2024-06-26 18:44:27 +05:30
ahmadshaheer1
580198ca7a feat: remove pagination for single-page lists 2024-06-26 18:44:27 +05:30
Vikrant Gupta
2fb5b16840 fix: search not working in services table (#5353) 2024-06-26 16:30:11 +05:30
Srikanth Chekuri
de571aa69a chore: flaky TestTransformToTableForClickHouseQueries (#5355) 2024-06-26 16:19:24 +05:30
Srikanth Chekuri
daa5a05677 chore: update table response format (#5349) 2024-06-26 14:34:27 +05:30
CheetoDa
4f69996b9d fix: aks collector instruction fix (#5350) 2024-06-26 11:43:55 +05:30
SagarRajput-7
6c402d9e46 fix: added correct query for external metric service graph (#5318) 2024-06-25 21:31:52 +05:30
Prashant Shahi
c6e9eeeee6 Merge pull request #5348 from SigNoz/release/v0.48.1
Release/v0.48.1
2024-06-25 19:54:14 +05:30
Prashant Shahi
97b66741a7 chore(signoz): 📌 pin versions: SigNoz 0.48.1
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-06-25 17:59:37 +05:30
Prashant Shahi
6b234da969 Merge branch 'main' into release/v0.48.1 2024-06-25 17:58:39 +05:30
Vishal Sharma
51032f6caa chore: move posthog to segment (#5330) 2024-06-25 15:50:09 +05:30
Vikrant Gupta
41f91db622 fix: apdex tooltip not visible in service details page (#5346) 2024-06-25 14:01:49 +05:30
Srikanth Chekuri
52e0303997 fix: table order by with builder queries (#5308) 2024-06-25 13:42:40 +05:30
Vikrant Gupta
5df25e83d1 fix: make the license key check case insensitive (#5331)
* fix: make the license key check case insensitive

* fix: added safety checks
2024-06-25 10:56:52 +05:30
Srikanth Chekuri
873280abea chore: read double pointer numbers from result (#5300) 2024-06-25 10:32:44 +05:30
Srikanth Chekuri
8ccdc71eaf chore: remove deprecated option (#5239) 2024-06-25 10:10:33 +05:30
Prashant Shahi
d5f156a6e9 ci(push): include POSTHOG_KEY environment in frontend (#5327)
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-06-24 19:24:42 +05:30
Vikrant Gupta
cc7559ddee fix: stacked series no data case (#5328) 2024-06-24 18:17:34 +05:30
Yunus M
415057c260 feat: go to traces should use start time and end time from trace details (#5326)
* feat: go to traces should use start time and endtime from trace details

* chore: remove console log

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
2024-06-24 16:57:05 +05:30
Vishal Sharma
89b67b8880 chore: posthog js init (#5324)
* chore: posthog js init

* feat: posthog events

---------

Co-authored-by: YounixM <myounis.ar@live.com>
2024-06-24 16:48:25 +05:30
Vishal Sharma
878cb7c0a6 fix: trace detail api start and end time (#5325) 2024-06-24 16:07:42 +05:30
Srikanth Chekuri
0375fc47a7 chore: add start/end millis for trace details response (#5321) 2024-06-24 14:45:26 +05:30
Srikanth Chekuri
a7a160df76 fix: incorrect telemetry query for samples (#5314) 2024-06-24 09:23:18 +05:30
Yunus M
8cd60b5c60 fix: handle overflow for attribute tooltips in trace details page (#5313) 2024-06-22 15:41:42 +05:30
CheetoDa
8ff392bc96 feat: azure monitoring docs (#5159)
* feat: azure monitoring docs

* chore: mapped paths

* chore: fixed instructions

* fix: added central collector steps

* fix: handle default azure steps, card alignment and reload issues

* fix: removed return true

---------

Co-authored-by: YounixM <myounis.ar@live.com>
2024-06-21 15:05:37 +05:30
SagarRajput-7
b59d9c7b90 fix: handled value as string from queryParams for trace filters (#5274)
* fix: handled value as string from queryParams for trace filters

* fix: added isArray check
2024-06-21 12:55:27 +05:30
Rajat Dabade
afcee9cd02 refactor: add to query should not open log detail drawer (#4732)
Co-authored-by: Rajat-Dabade <rajat@signoz.io>
2024-06-21 11:46:04 +05:30
Prashant Shahi
9dbef080c6 Merge pull request #5288 from SigNoz/release/v0.48.x
Release/v0.48.x
2024-06-20 20:49:47 +05:30
Vikrant Gupta
82a079e687 fix: move date time picker to click rather than hover (#5296) 2024-06-20 19:19:42 +05:30
568 changed files with 25216 additions and 2946 deletions

View File

@@ -158,6 +158,7 @@ jobs:
echo 'SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
- name: Install dependencies
working-directory: frontend
run: yarn install

View File

@@ -30,6 +30,7 @@ jobs:
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
GCP_ZONE: ${{ secrets.GCP_ZONE }}
GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }}
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
run: |
read -r -d '' COMMAND <<EOF || true
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
@@ -51,4 +52,4 @@ jobs:
make build-frontend-amd64
make run-testing
EOF
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"
gcloud beta compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"

View File

@@ -30,6 +30,7 @@ jobs:
GCP_PROJECT: ${{ secrets.GCP_PROJECT }}
GCP_ZONE: ${{ secrets.GCP_ZONE }}
GCP_INSTANCE: ${{ secrets.GCP_INSTANCE }}
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
run: |
read -r -d '' COMMAND <<EOF || true
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
@@ -52,4 +53,4 @@ jobs:
make build-frontend-amd64
make run-testing
EOF
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"
gcloud beta compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"

View File

@@ -347,7 +347,7 @@ curl -sL https://github.com/SigNoz/signoz/raw/develop/sample-apps/hotrod/hotrod-
```bash
kubectl -n sample-application run strzal --image=djbingham/curl \
--restart='OnFailure' -i --tty --rm --command -- curl -X POST -F \
'locust_count=6' -F 'hatch_rate=2' http://locust-master:8089/swarm
'user_count=6' -F 'spawn_rate=2' http://locust-master:8089/swarm
```
**5.1.3 To stop the load generation:**

View File

@@ -188,3 +188,4 @@ test:
go test ./pkg/query-service/tests/integration/...
go test ./pkg/query-service/rules/...
go test ./pkg/query-service/collectorsimulator/...
go test ./pkg/query-service/postprocess/...

View File

@@ -198,14 +198,14 @@ Not sure how to get started? Just ping us on `#contributing` in our [slack commu
#### Frontend
- [Palash Gupta](https://github.com/palashgdev)
- [Yunus M](https://github.com/YounixM)
- [Rajat Dabade](https://github.com/Rajat-Dabade)
- [Vikrant Gupta](https://github.com/vikrantgupta25)
- [Sagar Rajput](https://github.com/SagarRajput-7)
#### DevOps
- [Prashant Shahi](https://github.com/prashant-shahi)
- [Dhawal Sanghvi](https://github.com/dhawal1248)
- [Vibhu Pandey](https://github.com/grandwizard28)
<br /><br />

View File

@@ -146,7 +146,7 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.48.0
image: signoz/query-service:0.49.1
command:
[
"-config=/root/config/prometheus.yml",
@@ -199,7 +199,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
image: signoz/signoz-otel-collector:0.102.0
image: signoz/signoz-otel-collector:0.102.2
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -211,6 +211,7 @@ services:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /:/hostfs:ro
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name={{.Node.Hostname}},os.type={{.Node.Platform.OS}},dockerswarm.service.name={{.Service.Name}},dockerswarm.task.name={{.Task.Name}}
- DOCKER_MULTI_NODE_CLUSTER=false
@@ -237,7 +238,7 @@ services:
- query-service
otel-collector-migrator:
image: signoz/signoz-schema-migrator:0.102.0
image: signoz/signoz-schema-migrator:0.102.2
deploy:
restart_policy:
condition: on-failure

View File

@@ -36,6 +36,7 @@ receivers:
# endpoint: 0.0.0.0:6832
hostmetrics:
collection_interval: 30s
root_path: /hostfs
scrapers:
cpu: {}
load: {}

View File

@@ -66,7 +66,7 @@ services:
- --storage.path=/data
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.0}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -81,7 +81,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector:
container_name: signoz-otel-collector
image: signoz/signoz-otel-collector:0.102.0
image: signoz/signoz-otel-collector:0.102.2
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -93,6 +93,8 @@ services:
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /:/hostfs:ro
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
ports:

View File

@@ -164,7 +164,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.48.0}
image: signoz/query-service:${DOCKER_TAG:-0.49.1}
container_name: signoz-query-service
command:
[
@@ -204,7 +204,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.48.0}
image: signoz/frontend:${DOCKER_TAG:-0.49.1}
container_name: signoz-frontend
restart: on-failure
depends_on:
@@ -216,7 +216,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.0}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -230,7 +230,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.0}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2}
container_name: signoz-otel-collector
command:
[
@@ -244,6 +244,7 @@ services:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /:/hostfs:ro
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
- DOCKER_MULTI_NODE_CLUSTER=false

View File

@@ -164,7 +164,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.48.0}
image: signoz/query-service:${DOCKER_TAG:-0.49.1}
container_name: signoz-query-service
command:
[
@@ -203,7 +203,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.48.0}
image: signoz/frontend:${DOCKER_TAG:-0.49.1}
container_name: signoz-frontend
restart: on-failure
depends_on:
@@ -215,7 +215,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.0}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -229,7 +229,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.0}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2}
container_name: signoz-otel-collector
command:
[
@@ -243,6 +243,7 @@ services:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /:/hostfs:ro
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
- DOCKER_MULTI_NODE_CLUSTER=false

View File

@@ -36,6 +36,7 @@ receivers:
# endpoint: 0.0.0.0:6832
hostmetrics:
collection_interval: 30s
root_path: /hostfs
scrapers:
cpu: {}
load: {}

View File

@@ -389,7 +389,7 @@ trap bye EXIT
URL="https://api.segment.io/v1/track"
HEADER_1="Content-Type: application/json"
HEADER_2="Authorization: Basic NEdtb2E0aXhKQVVIeDJCcEp4c2p3QTFiRWZud0VlUno6"
HEADER_2="Authorization: Basic OWtScko3b1BDR1BFSkxGNlFqTVBMdDVibGpGaFJRQnI="
send_event() {
error=""

View File

@@ -24,7 +24,6 @@ import (
type APIHandlerOptions struct {
DataConnector interfaces.DataConnector
SkipConfig *basemodel.SkipConfig
PreferDelta bool
PreferSpanMetrics bool
MaxIdleConns int
MaxOpenConns int
@@ -53,7 +52,6 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector,
SkipConfig: opts.SkipConfig,
PerferDelta: opts.PreferDelta,
PreferSpanMetrics: opts.PreferSpanMetrics,
MaxIdleConns: opts.MaxIdleConns,
MaxOpenConns: opts.MaxOpenConns,

View File

@@ -1,7 +1,9 @@
package api
import (
"errors"
"net/http"
"strings"
"github.com/gorilla/mux"
"go.signoz.io/signoz/pkg/query-service/app/dashboards"
@@ -29,6 +31,10 @@ func (ah *APIHandler) lockUnlockDashboard(w http.ResponseWriter, r *http.Request
// Get the dashboard UUID from the request
uuid := mux.Vars(r)["uuid"]
if strings.HasPrefix(uuid,"integration") {
RespondError(w, &model.ApiError{Typ: model.ErrorForbidden, Err: errors.New("dashboards created by integrations cannot be unlocked")}, "You are not authorized to lock/unlock this dashboard")
return
}
dashboard, err := dashboards.GetDashboard(r.Context(), uuid)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, err.Error())

View File

@@ -4,11 +4,11 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/http/httputil"
_ "net/http/pprof" // http profiler
"os"
"regexp"
@@ -28,6 +28,8 @@ import (
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
"go.signoz.io/signoz/ee/query-service/interfaces"
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/migrate"
"go.signoz.io/signoz/pkg/query-service/model"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
licensepkg "go.signoz.io/signoz/ee/query-service/license"
@@ -41,6 +43,7 @@ import (
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
"go.signoz.io/signoz/pkg/query-service/app/opamp"
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
"go.signoz.io/signoz/pkg/query-service/app/preferences"
"go.signoz.io/signoz/pkg/query-service/cache"
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/healthcheck"
@@ -64,7 +67,6 @@ type ServerOptions struct {
// alert specific params
DisableRules bool
RuleRepoURL string
PreferDelta bool
PreferSpanMetrics bool
MaxIdleConns int
MaxOpenConns int
@@ -111,6 +113,10 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
baseexplorer.InitWithDSN(baseconst.RELATIONAL_DATASOURCE_PATH)
if err := preferences.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH); err != nil {
return nil, err
}
localDB, err := dashboards.InitDB(baseconst.RELATIONAL_DATASOURCE_PATH)
if err != nil {
@@ -119,33 +125,13 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
localDB.SetMaxOpenConns(10)
gatewayFeature := basemodel.Feature{
Name: "GATEWAY",
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
}
//Activate this feature if the url is not empty
var gatewayProxy *httputil.ReverseProxy
if serverOptions.GatewayUrl == "" {
gatewayFeature.Active = false
gatewayProxy, err = gateway.NewNoopProxy()
if err != nil {
return nil, err
}
} else {
zap.L().Info("Enabling gateway feature flag ...")
gatewayFeature.Active = true
gatewayProxy, err = gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
if err != nil {
return nil, err
}
gatewayProxy, err := gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
if err != nil {
return nil, err
}
// initiate license manager
lm, err := licensepkg.StartManager("sqlite", localDB, gatewayFeature)
lm, err := licensepkg.StartManager("sqlite", localDB)
if err != nil {
return nil, err
}
@@ -194,6 +180,13 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err
}
go func() {
err = migrate.ClickHouseMigrate(reader.GetConn(), serverOptions.Cluster)
if err != nil {
zap.L().Error("error while running clickhouse migrations", zap.Error(err))
}
}()
// initiate opamp
_, err = opAmpModel.InitDB(localDB)
if err != nil {
@@ -256,7 +249,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
apiOpts := api.APIHandlerOptions{
DataConnector: reader,
SkipConfig: skipConfig,
PreferDelta: serverOptions.PreferDelta,
PreferSpanMetrics: serverOptions.PreferSpanMetrics,
MaxIdleConns: serverOptions.MaxIdleConns,
MaxOpenConns: serverOptions.MaxOpenConns,
@@ -342,7 +334,17 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
// add auth middleware
getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) {
return auth.GetUserFromRequest(r, apiHandler)
user, err := auth.GetUserFromRequest(r, apiHandler)
if err != nil {
return nil, err
}
if user.User.OrgId == "" {
return nil, model.UnauthorizedError(errors.New("orgId is missing in the claims"))
}
return user, nil
}
am := baseapp.NewAuthMiddleware(getUserFromRequest)
@@ -734,6 +736,7 @@ func makeRulesManager(
DisableRules: disableRules,
FeatureFlags: fm,
Reader: ch,
EvalDelay: baseconst.GetEvalDelay(),
}
// create Manager

View File

@@ -20,11 +20,14 @@ import (
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, basemodel.BaseApiError) {
// get auth domain from email domain
domain, apierr := m.GetDomainByEmail(ctx, email)
if apierr != nil {
zap.L().Error("failed to get domain from email", zap.Error(apierr))
return nil, model.InternalErrorStr("failed to get domain from email")
}
if domain == nil {
zap.L().Error("email domain does not match any authenticated domain", zap.String("email", email))
return nil, model.InternalErrorStr("email domain does not match any authenticated domain")
}
hash, err := baseauth.PasswordHash(utils.GeneratePassowrd())
if err != nil {

View File

@@ -5,5 +5,5 @@ import (
)
func NewNoopProxy() (*httputil.ReverseProxy, error) {
return nil, nil
return &httputil.ReverseProxy{}, nil
}

View File

@@ -89,7 +89,6 @@ func main() {
var cacheConfigPath, fluxInterval string
var enableQueryServiceLogOTLPExport bool
var preferDelta bool
var preferSpanMetrics bool
var maxIdleConns int
@@ -100,14 +99,13 @@ func main() {
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
flag.BoolVar(&preferDelta, "prefer-delta", false, "(prefer delta over cumulative metrics)")
flag.BoolVar(&preferSpanMetrics, "prefer-span-metrics", false, "(prefer span metrics for service level metrics)")
flag.IntVar(&maxIdleConns, "max-idle-conns", 50, "(number of connections to maintain in the pool.)")
flag.IntVar(&maxOpenConns, "max-open-conns", 100, "(max connections for use at any time.)")
flag.DurationVar(&dialTimeout, "dial-timeout", 5*time.Second, "(the maximum time to establish a connection.)")
flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)")
flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)")
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(cache config to use)")
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(the interval to exclude data from being cached to avoid incorrect cache for data in motion)")
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
@@ -125,7 +123,6 @@ func main() {
HTTPHostPort: baseconst.HTTPHostPort,
PromConfigPath: promConfigPath,
SkipTopLvlOpsPath: skipTopLvlOpsPath,
PreferDelta: preferDelta,
PreferSpanMetrics: preferSpanMetrics,
PrivateHostPort: baseconst.PrivateHostPort,
DisableRules: disableRules,

View File

@@ -11,6 +11,7 @@ const Enterprise = "ENTERPRISE_PLAN"
const DisableUpsell = "DISABLE_UPSELL"
const Onboarding = "ONBOARDING"
const ChatSupport = "CHAT_SUPPORT"
const Gateway = "GATEWAY"
var BasicPlan = basemodel.FeatureSet{
basemodel.Feature{
@@ -111,6 +112,13 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: Gateway,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var ProPlan = basemodel.FeatureSet{
@@ -205,6 +213,13 @@ var ProPlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: Gateway,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var EnterprisePlan = basemodel.FeatureSet{
@@ -313,4 +328,11 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: Gateway,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
}

View File

@@ -9,6 +9,7 @@ const config: Config.InitialOptions = {
modulePathIgnorePatterns: ['dist'],
moduleNameMapper: {
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
},
globals: {
extensionsToTreatAsEsm: ['.ts'],

View File

@@ -88,6 +88,7 @@
"lucide-react": "0.379.0",
"mini-css-extract-plugin": "2.4.5",
"papaparse": "5.4.1",
"posthog-js": "1.142.1",
"rc-tween-one": "3.0.6",
"react": "18.2.0",
"react-addons-update": "15.6.3",
@@ -109,6 +110,8 @@
"react-syntax-highlighter": "15.5.0",
"react-use": "^17.3.2",
"react-virtuoso": "4.0.3",
"overlayscrollbars-react": "^0.5.6",
"overlayscrollbars": "^2.8.1",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"rehype-raw": "7.0.0",

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a" x1="2.94" y1="3.74" x2="8.67" y2="3.74" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="b" x1="9.13" y1="3.79" x2="14.85" y2="3.79" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="c" x1=".01" y1="9.12" x2="5.73" y2="9.12" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="d" x1="6.18" y1="9.08" x2="11.9" y2="9.08" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="e" x1="12.35" y1="9.13" x2="18.08" y2="9.13" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="f" x1="2.87" y1="14.56" x2="8.6" y2="14.56" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="g" x1="9.05" y1="14.6" x2="14.78" y2="14.6" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient></defs><path fill="url(#a)" d="M5.8 1.22l-2.86.53v3.9l2.86.61 2.87-1.15V2.2L5.8 1.22z"/><path d="M5.91 6.2l2.62-1.06A.2.2 0 008.65 5V2.36a.21.21 0 00-.13-.18l-2.65-.9h-.12l-2.6.48a.2.2 0 00-.15.18v3.53a.19.19 0 00.15.19l2.63.55a.32.32 0 00.13-.01z" fill="none"/><path d="M2.94 1.75v3.9l2.89.61v-5zm1.22 3.6l-.81-.16v-3l.81-.13zm1.26.23l-.93-.15V2l.93-.16z" fill="#341a6e"/><path fill="url(#b)" d="M11.99 1.27l-2.86.53v3.9l2.86.61 2.86-1.16v-2.9l-2.86-.98z"/><path d="M9.13 1.8v3.9l2.87.61v-5zm1.21 3.6l-.81-.16v-3l.81-.13zm1.26.23l-.93-.15V2.05l.93-.17z" fill="#341a6e"/><path fill="url(#c)" d="M2.87 6.6l-2.86.53v3.9l2.86.61 2.87-1.15V7.58L2.87 6.6z"/><path d="M0 7.13V11l2.89.61v-5zm1.21 3.61l-.81-.17v-3l.81-.14zm1.27.26l-.93-.15V7.38l.93-.16z" fill="#341a6e"/><path fill="url(#d)" d="M9.04 6.56l-2.86.53v3.9l2.86.62 2.86-1.16V7.54l-2.86-.98z"/><path d="M6.18 7.09V11l2.88.61v-5zm1.21 3.61l-.81-.17v-3l.81-.14zm1.26.22l-.93-.15V7.34l.93-.16z" fill="#341a6e"/><path fill="url(#e)" d="M15.21 6.61l-2.86.53v3.9l2.86.61 2.87-1.15V7.59l-2.87-.98z"/><path d="M12.35 7.14V11l2.89.61v-5zm1.22 3.61l-.81-.17v-3l.81-.14zm1.26.22l-.93-.15V7.39l.93-.16z" fill="#341a6e"/><path fill="url(#f)" d="M5.73 12.04l-2.86.52v3.9l2.86.62 2.87-1.16v-2.9l-2.87-.98z"/><path d="M5.84 17l2.61-1a.18.18 0 00.12-.18v-2.6a.2.2 0 00-.13-.22l-2.64-.9a.17.17 0 00-.12 0l-2.6.47a.19.19 0 00-.16.19v3.54a.19.19 0 00.15.19L5.7 17a.23.23 0 00.14 0z" fill="none"/><path d="M2.87 12.56v3.9l2.89.62V12zm1.22 3.61L3.28 16v-3l.81-.14zm1.26.23l-.93-.15v-3.44l.93-.16z" fill="#341a6e"/><path fill="url(#g)" d="M11.91 12.08l-2.86.53v3.9l2.86.61 2.87-1.15v-2.91l-2.87-.98z"/><path d="M9.05 12.61v3.9l2.89.61v-5zm1.22 3.61l-.81-.17v-3l.81-.14zm1.26.22l-.93-.15v-3.43l.93-.16z" fill="#341a6e"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="b" x1="4.4" y1="11.48" x2="4.37" y2="7.53" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><linearGradient id="c" x1="10.13" y1="15.45" x2="10.13" y2="11.9" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><linearGradient id="d" x1="14.18" y1="11.15" x2="14.18" y2="7.38" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><radialGradient id="a" cx="13428.81" cy="3518.86" r="56.67" gradientTransform="matrix(.15 0 0 .15 -2005.33 -518.83)" gradientUnits="userSpaceOnUse"><stop offset=".18" stop-color="#5ea0ef"/><stop offset="1" stop-color="#0078d4"/></radialGradient></defs><path d="M14.21 15.72A8.5 8.5 0 013.79 2.28l.09-.06a8.5 8.5 0 0110.33 13.5" fill="url(#a)"/><path d="M6.69 7.23a13 13 0 018.91-3.58 8.47 8.47 0 00-1.49-1.44 14.34 14.34 0 00-4.69 1.1 12.54 12.54 0 00-4.08 2.82 2.76 2.76 0 011.35 1.1zM2.48 10.65a17.86 17.86 0 00-.83 2.62 7.82 7.82 0 00.62.92c.18.23.35.44.55.65a17.94 17.94 0 011.08-3.47 2.76 2.76 0 01-1.42-.72z" fill="#fff" opacity=".6"/><path d="M3.46 6.11a12 12 0 01-.69-2.94 8.15 8.15 0 00-1.1 1.45A12.69 12.69 0 002.24 7a2.69 2.69 0 011.22-.89z" fill="#f2f2f2" opacity=".55"/><circle cx="4.38" cy="8.68" r="2.73" fill="url(#b)"/><path d="M8.36 13.67a1.77 1.77 0 01.54-1.27 11.88 11.88 0 01-2.53-1.86 2.74 2.74 0 01-1.49.83 13.1 13.1 0 001.45 1.28 12.12 12.12 0 002.05 1.25 1.79 1.79 0 01-.02-.23zM14.66 13.88a12 12 0 01-2.76-.32.41.41 0 010 .11 1.75 1.75 0 01-.51 1.24 13.69 13.69 0 003.42.24A8.21 8.21 0 0016 13.81a11.5 11.5 0 01-1.34.07z" fill="#f2f2f2" opacity=".55"/><circle cx="10.13" cy="13.67" r="1.78" fill="url(#c)"/><path d="M12.32 8.93a1.83 1.83 0 01.61-1 25.5 25.5 0 01-4.46-4.14 16.91 16.91 0 01-2-2.92 7.64 7.64 0 00-1.09.42 18.14 18.14 0 002.15 3.18 26.44 26.44 0 004.79 4.46z" fill="#f2f2f2" opacity=".7"/><circle cx="14.18" cy="9.27" r="1.89" fill="url(#d)"/><path d="M17.35 10.54l-.35-.17-.3-.16h-.06l-.26-.21h-.07L16 9.8a1.76 1.76 0 01-.64.92c.12.08.25.15.38.22l.08.05.35.19.86.45a8.63 8.63 0 00.29-1.11z" fill="#f2f2f2" opacity=".55"/><circle cx="4.38" cy="8.68" r="2.73" fill="url(#b)"/><circle cx="10.13" cy="13.67" r="1.78" fill="url(#c)"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><defs><linearGradient id="b27f1ad0-7d11-4247-9da3-91bce6211f32" x1="8.798" y1="8.703" x2="14.683" y2="8.703" gradientUnits="userSpaceOnUse"><stop offset="0.001" stop-color="#773adc" /><stop offset="1" stop-color="#552f99" /></linearGradient><linearGradient id="b2f92112-4ca9-4b17-a019-c9f26c1a4a8f" x1="5.764" y1="3.777" x2="5.764" y2="13.78" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a67af4" /><stop offset="0.999" stop-color="#773adc" /></linearGradient></defs><g id="b8a0486a-5501-4d92-b540-a766c4b3b548"><g><g><g><path d="M16.932,11.578a8.448,8.448,0,0,1-7.95,5.59,8.15,8.15,0,0,1-2.33-.33,2.133,2.133,0,0,0,.18-.83c.01,0,.03.01.04.01a7.422,7.422,0,0,0,2.11.3,7.646,7.646,0,0,0,6.85-4.28l.01-.01Z" fill="#32bedd" /><path d="M3.582,14.068a2.025,2.025,0,0,0-.64.56,8.6,8.6,0,0,1-1.67-2.44l1.04.23v.26a.6.6,0,0,0,.47.59l.14.03a6.136,6.136,0,0,0,.62.73Z" fill="#32bedd" /><path d="M12.352.958a2.28,2.28,0,0,0-.27.81c-.02-.01-.05-.02-.07-.03a7.479,7.479,0,0,0-3.03-.63,7.643,7.643,0,0,0-5.9,2.8l-.29.06a.6.6,0,0,0-.48.58v.46l-1.02.19A8.454,8.454,0,0,1,8.982.268,8.6,8.6,0,0,1,12.352.958Z" fill="#32bedd" /><path d="M16.872,5.7l-1.09-.38a6.6,6.6,0,0,0-.72-1.16c-.02-.03-.04-.05-.05-.07a2.083,2.083,0,0,0,.72-.45A7.81,7.81,0,0,1,16.872,5.7Z" fill="#32bedd" /><path d="M10.072,11.908l2.54.56L8.672,14.1c-.02,0-.03.01-.05.01a.154.154,0,0,1-.15-.15V3.448a.154.154,0,0,1,.15-.15.09.09,0,0,1,.05.01l4.46,1.56-3.05.57a.565.565,0,0,0-.44.54v5.4A.537.537,0,0,0,10.072,11.908Z" fill="#fff" /><g><g id="e918f286-5032-4942-ad29-ea17e6f1cc90"><path d="M1.1,5.668l1.21-.23v6.55l-1.23-.27-.99-.22a.111.111,0,0,1-.09-.12v-5.4a.12.12,0,0,1,.09-.12Z" fill="#a67af4" /></g><g><g id="a47a99dd-4d47-4c70-8c42-c5ac274ce496"><g><path d="M10.072,11.908l2.54.56L8.672,14.1c-.02,0-.03.01-.05.01a.154.154,0,0,1-.15-.15V3.448a.154.154,0,0,1,.15-.15.09.09,0,0,1,.05.01l4.46,1.56-3.05.57a.565.565,0,0,0-.44.54v5.4A.537.537,0,0,0,10.072,11.908Z" fill="url(#b27f1ad0-7d11-4247-9da3-91bce6211f32)" /><path d="M8.586,3.3,2.878,4.378a.177.177,0,0,0-.14.175V12.68a.177.177,0,0,0,.137.174L8.581,14.1a.176.176,0,0,0,.21-.174V3.478A.175.175,0,0,0,8.619,3.3Z" fill="url(#b2f92112-4ca9-4b17-a019-c9f26c1a4a8f)" /></g></g><polygon points="5.948 4.921 5.948 12.483 7.934 12.814 7.934 4.564 5.948 4.921" fill="#b796f9" opacity="0.5" /><polygon points="3.509 5.329 3.509 11.954 5.238 12.317 5.238 5.031 3.509 5.329" fill="#b796f9" opacity="0.5" /></g></g></g><path d="M16,2.048a1.755,1.755,0,1,1-1.76-1.76A1.756,1.756,0,0,1,16,2.048Z" fill="#32bedd" /><circle cx="4.65" cy="15.973" r="1.759" fill="#32bedd" /></g><path d="M18,6.689v3.844a.222.222,0,0,1-.133.2l-.766.316-3.07,1.268-.011,0a.126.126,0,0,1-.038,0,.1.1,0,0,1-.1-.1V5.234a.1.1,0,0,1,.054-.088l0,0,.019,0a.031.031,0,0,1,.019,0,.055.055,0,0,1,.034.008l.011,0,.012,0L17.05,6.2l.8.282A.213.213,0,0,1,18,6.689Z" fill="#773adc" /><path d="M13.959,5.14l-3.8.715a.118.118,0,0,0-.093.117v5.409a.118.118,0,0,0,.091.116l3.8.831a.115.115,0,0,0,.137-.09.109.109,0,0,0,0-.026V5.256a.117.117,0,0,0-.115-.118A.082.082,0,0,0,13.959,5.14Z" fill="#a67af4" /></g></g></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a" x1="-175.993" y1="-343.723" x2="-175.993" y2="-359.232" gradientTransform="matrix(1.156 0 0 1.029 212.573 370.548)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fea11b"/><stop offset=".284" stop-color="#fea51a"/><stop offset=".547" stop-color="#feb018"/><stop offset=".8" stop-color="#ffc314"/><stop offset="1" stop-color="#ffd70f"/></linearGradient></defs><path d="M5.54 13.105l-.586.588a.267.267 0 01-.377 0L.223 9.353a.533.533 0 010-.755l.588-.59 4.732 4.718a.267.267 0 010 .378z" fill="#50e6ff"/><path d="M4.863 4.305l.59.588a.267.267 0 010 .378L.806 9.932l-.59-.589a.533.533 0 01-.001-.754l4.273-4.285a.267.267 0 01.376 0z" fill="#1490df"/><path d="M17.19 8.012l.588.59a.533.533 0 01-.001.754l-4.354 4.34a.267.267 0 01-.377 0l-.586-.587a.267.267 0 010-.377l4.732-4.718z" fill="#50e6ff"/><path d="M17.782 9.34l-.59.589-4.648-4.662a.267.267 0 010-.377l.59-.588a.267.267 0 01.378 0l4.273 4.286a.533.533 0 010 .753z" fill="#1490df"/><path d="M8.459 9.9H4.87a.193.193 0 01-.2-.181.166.166 0 01.018-.075L8.991 1.13a.206.206 0 01.186-.106h4.245a.193.193 0 01.2.181.165.165 0 01-.035.1L8.534 7.966h4.928a.193.193 0 01.2.181.176.176 0 01-.052.122l-8.189 8.519c-.077.046-.624.5-.356-.189z" fill="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><radialGradient id="b" cx="9.36" cy="10.57" r="7.07" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2f2f2"/><stop offset=".58" stop-color="#eee"/><stop offset="1" stop-color="#e6e6e6"/></radialGradient><linearGradient id="a" x1="2.59" y1="10.16" x2="15.41" y2="10.16" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#005ba1"/><stop offset=".07" stop-color="#0060a9"/><stop offset=".36" stop-color="#0071c8"/><stop offset=".52" stop-color="#0078d4"/><stop offset=".64" stop-color="#0074cd"/><stop offset=".82" stop-color="#006abb"/><stop offset="1" stop-color="#005ba1"/></linearGradient></defs><path d="M9 5.14c-3.54 0-6.41-1-6.41-2.32v12.36c0 1.27 2.82 2.3 6.32 2.32H9c3.54 0 6.41-1 6.41-2.32V2.82c0 1.29-2.87 2.32-6.41 2.32z" fill="url(#a)"/><path d="M15.41 2.82c0 1.29-2.87 2.32-6.41 2.32s-6.41-1-6.41-2.32S5.46.5 9 .5s6.41 1 6.41 2.32" fill="#e8e8e8"/><path d="M13.92 2.63c0 .82-2.21 1.48-4.92 1.48s-4.92-.66-4.92-1.48S6.29 1.16 9 1.16s4.92.66 4.92 1.47" fill="#50e6ff"/><path d="M9 3a11.55 11.55 0 00-3.89.57A11.42 11.42 0 009 4.11a11.15 11.15 0 003.89-.58A11.84 11.84 0 009 3z" fill="#198ab3"/><path d="M12.9 11.4V8H12v4.13h2.46v-.73zM5.76 9.73a1.83 1.83 0 01-.51-.31.44.44 0 01-.12-.32.34.34 0 01.15-.3.68.68 0 01.42-.12 1.62 1.62 0 011 .29v-.86a2.58 2.58 0 00-1-.16 1.64 1.64 0 00-1.09.34 1.08 1.08 0 00-.42.89c0 .51.32.91 1 1.21a2.88 2.88 0 01.62.36.42.42 0 01.15.32.38.38 0 01-.16.31.81.81 0 01-.45.11 1.66 1.66 0 01-1.09-.42V12a2.17 2.17 0 001.07.24 1.88 1.88 0 001.18-.33 1.08 1.08 0 00.33-.91 1.05 1.05 0 00-.25-.7 2.42 2.42 0 00-.83-.57zM11 11.32a2.34 2.34 0 00.33-1.26A2.32 2.32 0 0011 9a1.81 1.81 0 00-.7-.75 2 2 0 00-1-.26 2.11 2.11 0 00-1.08.27 1.86 1.86 0 00-.73.74 2.46 2.46 0 00-.26 1.14 2.26 2.26 0 00.24 1 1.76 1.76 0 00.69.74 2.06 2.06 0 001 .3l.86 1h1.21L10 12.08a1.79 1.79 0 001-.76zm-1-.25a.94.94 0 01-.76.35.92.92 0 01-.76-.36 1.52 1.52 0 01-.29-1 1.53 1.53 0 01.29-1 1 1 0 01.78-.37.87.87 0 01.75.37 1.62 1.62 0 01.27 1 1.46 1.46 0 01-.28 1.01z" fill="url(#b)"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a" x1="8.88" y1="12.21" x2="8.88" y2=".21" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0078d4"/><stop offset=".82" stop-color="#5ea0ef"/></linearGradient><linearGradient id="b" x1="8.88" y1="16.84" x2="8.88" y2="12.21" gradientUnits="userSpaceOnUse"><stop offset=".15" stop-color="#ccc"/><stop offset="1" stop-color="#707070"/></linearGradient></defs><rect x="-.12" y=".21" width="18" height="12" rx=".6" fill="url(#a)"/><path fill="#50e6ff" d="M11.88 4.46v3.49l-3 1.76v-3.5l3-1.75z"/><path fill="#c3f1ff" d="M11.88 4.46l-3 1.76-3-1.76 3-1.75 3 1.75z"/><path fill="#9cebff" d="M8.88 6.22v3.49l-3-1.76V4.46l3 1.76z"/><path fill="#c3f1ff" d="M5.88 7.95l3-1.74v3.5l-3-1.76z"/><path fill="#9cebff" d="M11.88 7.95l-3-1.74v3.5l3-1.76z"/><path d="M12.49 15.84c-1.78-.28-1.85-1.56-1.85-3.63H7.11c0 2.07-.06 3.35-1.84 3.63a1 1 0 00-.89 1h9a1 1 0 00-.89-1z" fill="url(#b)"/></svg>

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

View File

@@ -0,0 +1,8 @@
{
"invite_user": "Invite your teammates",
"invite": "Invite",
"skip": "Skip",
"invite_user_helper_text": "Not the right person to get started? No worries! Invite someone who can.",
"select_use_case": "Select a use-case to get started",
"get_started": "Get Started"
}

View File

@@ -6,5 +6,6 @@
"share": "Share",
"save": "Save",
"edit": "Edit",
"logged_in": "Logged In"
"logged_in": "Logged In",
"pending_data_placeholder": "Just a bit of patience, just a little bits enough ⎯ were getting your {{dataSource}}!"
}

View File

@@ -0,0 +1,8 @@
{
"invite_user": "Invite your teammates",
"invite": "Invite",
"skip": "Skip",
"invite_user_helper_text": "Not the right person to get started? No worries! Invite someone who can.",
"select_use_case": "Select a use-case to get started",
"get_started": "Get Started"
}

View File

@@ -8,6 +8,7 @@
"GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs",
"GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure",
"GET_STARTED_AWS_MONITORING": "SigNoz | Get Started | AWS",
"GET_STARTED_AZURE_MONITORING": "SigNoz | Get Started | AZURE",
"TRACE": "SigNoz | Trace",
"TRACE_DETAIL": "SigNoz | Trace Detail",
"TRACES_EXPLORER": "SigNoz | Traces Explorer",

View File

@@ -76,9 +76,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
isUserFetching: false,
},
});
if (!isLoggedIn) {
history.push(ROUTES.LOGIN);
history.push(ROUTES.LOGIN, { from: pathname });
}
};

View File

@@ -1,6 +1,7 @@
import { ConfigProvider } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import logEvent from 'api/common/logEvent';
import NotFound from 'components/NotFound';
import Spinner from 'components/Spinner';
import { FeatureKeys } from 'constants/features';
@@ -17,6 +18,7 @@ import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import { identity, pick, pickBy } from 'lodash-es';
import posthog from 'posthog-js';
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useEffect, useState } from 'react';
@@ -38,7 +40,7 @@ import defaultRoutes, {
function App(): JSX.Element {
const themeConfig = useThemeConfig();
const { data } = useLicense();
const { data: licenseData } = useLicense();
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
const { role, isLoggedIn: isLoggedInState, user, org } = useSelector<
AppState,
@@ -47,7 +49,7 @@ function App(): JSX.Element {
const dispatch = useDispatch<Dispatch<AppActions>>();
const { trackPageView, trackEvent } = useAnalytics();
const { trackPageView } = useAnalytics();
const { hostname, pathname } = window.location;
@@ -92,10 +94,10 @@ function App(): JSX.Element {
});
const isOnBasicPlan =
data?.payload?.licenses?.some(
licenseData?.payload?.licenses?.some(
(license) =>
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
) || data?.payload?.licenses === null;
) || licenseData?.payload?.licenses === null;
const enableAnalytics = (user: User): void => {
const orgName =
@@ -112,9 +114,7 @@ function App(): JSX.Element {
};
const sanitizedIdentifyPayload = pickBy(identifyPayload, identity);
const domain = extractDomain(email);
const hostNameParts = hostname.split('.');
const groupTraits = {
@@ -127,10 +127,30 @@ function App(): JSX.Element {
};
window.analytics.identify(email, sanitizedIdentifyPayload);
window.analytics.group(domain, groupTraits);
window.clarity('identify', email, name);
posthog?.identify(email, {
email,
name,
orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
tenant_url: hostname,
company_domain: domain,
source: 'signoz-ui',
isPaidUser: !!licenseData?.payload?.trialConvertedToSubscription,
});
posthog?.group('company', domain, {
name: orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
tenant_url: hostname,
company_domain: domain,
source: 'signoz-ui',
isPaidUser: !!licenseData?.payload?.trialConvertedToSubscription,
});
};
useEffect(() => {
@@ -144,10 +164,6 @@ function App(): JSX.Element {
!isIdentifiedUser
) {
setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
if (isCloudUserVal) {
enableAnalytics(user);
}
}
if (
@@ -184,7 +200,7 @@ function App(): JSX.Element {
LOCALSTORAGE.THEME_ANALYTICS_V1,
);
if (!isThemeAnalyticsSent) {
trackEvent('Theme Analytics', {
logEvent('Theme Analytics', {
theme: isDarkMode ? THEME_MODE.DARK : THEME_MODE.LIGHT,
user: pick(user, ['email', 'userId', 'name']),
org,
@@ -195,6 +211,11 @@ function App(): JSX.Element {
console.error('Failed to parse local storage theme analytics event');
}
}
if (isCloudUserVal && user && user.email) {
enableAnalytics(user);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user]);

View File

@@ -9,9 +9,9 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
// making the error status code as standard Error Status Code
const statusCode = response.status as ErrorStatusCode;
if (statusCode >= 400 && statusCode < 500) {
const { data } = response as AxiosResponse;
const { data } = response as AxiosResponse;
if (statusCode >= 400 && statusCode < 500) {
if (statusCode === 404) {
return {
statusCode,
@@ -34,12 +34,11 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
body: JSON.stringify((response.data as any).data),
};
}
return {
statusCode,
payload: null,
error: 'Something went wrong',
message: null,
message: data?.error,
};
}
if (request) {

View File

@@ -3,7 +3,7 @@ const apiV1 = '/api/v1/';
export const apiV2 = '/api/v2/';
export const apiV3 = '/api/v3/';
export const apiV4 = '/api/v4/';
export const gatewayApiV1 = '/api/gateway/v1';
export const apiAlertManager = '/api/alertmanager';
export const gatewayApiV1 = '/api/gateway/v1/';
export const apiAlertManager = '/api/alertmanager/';
export default apiV1;

View File

@@ -1,4 +1,4 @@
import axios from 'api';
import { ApiBaseInstance as axios } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
@@ -21,6 +21,7 @@ const logEvent = async (
payload: response.data.data,
};
} catch (error) {
console.error(error);
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -96,6 +96,10 @@ const interceptorRejected = async (
}
};
const interceptorRejectedBase = async (
value: AxiosResponse<any>,
): Promise<AxiosResponse<any>> => Promise.reject(value);
const instance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${apiV1}`,
});
@@ -140,6 +144,18 @@ ApiV4Instance.interceptors.response.use(
ApiV4Instance.interceptors.request.use(interceptorsRequestResponse);
//
// axios Base
export const ApiBaseInstance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${apiV1}`,
});
ApiBaseInstance.interceptors.response.use(
interceptorsResponse,
interceptorRejectedBase,
);
ApiBaseInstance.interceptors.request.use(interceptorsRequestResponse);
//
// gateway Api V1
export const GatewayApiV1Instance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${gatewayApiV1}`,

View File

@@ -1,7 +1,20 @@
import axios from 'api';
import { isNil } from 'lodash-es';
const getTopLevelOperations = async (): Promise<ServiceDataProps> => {
const response = await axios.post(`/service/top_level_operations`);
interface GetTopLevelOperationsProps {
service?: string;
start?: number;
end?: number;
}
const getTopLevelOperations = async (
props: GetTopLevelOperationsProps,
): Promise<ServiceDataProps> => {
const response = await axios.post(`/service/top_level_operations`, {
start: !isNil(props.start) ? `${props.start}` : undefined,
end: !isNil(props.end) ? `${props.end}` : undefined,
service: props.service,
});
return response.data;
};

View File

@@ -1,6 +1,7 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { Dayjs } from 'dayjs';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Recurrence } from './getAllDowntimeSchedules';
@@ -11,8 +12,8 @@ export interface DowntimeSchedulePayload {
alertIds: string[];
schedule: {
timezone?: string;
startTime?: string;
endTime?: string;
startTime?: string | Dayjs;
endTime?: string | Dayjs;
recurrence?: Recurrence;
};
}

View File

@@ -1,6 +1,6 @@
import axios from 'api';
import { AxiosError, AxiosResponse } from 'axios';
import { Option } from 'container/PlannedDowntime/DropdownWithSubMenu/DropdownWithSubMenu';
import { Option } from 'container/PlannedDowntime/PlannedDowntimeutils';
import { useQuery, UseQueryResult } from 'react-query';
export type Recurrence = {
@@ -28,6 +28,7 @@ export interface DowntimeSchedules {
createdBy: string | null;
updatedAt: string | null;
updatedBy: string | null;
kind: string | null;
}
export type PayloadProps = { data: DowntimeSchedules[] };

View File

@@ -287,7 +287,7 @@ function CustomTimePicker({
)
}
arrow={false}
trigger="hover"
trigger="click"
open={open}
onOpenChange={handleOpenChange}
style={{

View File

@@ -5,7 +5,13 @@ import { Button, Dropdown, MenuProps } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useState } from 'react';
function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
function DropDown({
element,
onDropDownItemClick,
}: {
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
const isDarkMode = useIsDarkMode();
const items: MenuProps['items'] = element.map(
@@ -23,6 +29,7 @@ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
items,
onMouseEnter: (): void => setDdOpen(true),
onMouseLeave: (): void => setDdOpen(false),
onClick: (item): void => onDropDownItemClick?.(item),
}}
open={isDdOpen}
>
@@ -40,4 +47,8 @@ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
);
}
DropDown.defaultProps = {
onDropDownItemClick: (): void => {},
};
export default DropDown;

View File

@@ -1,6 +1,7 @@
import { DrawerProps } from 'antd';
import { AddToQueryHOCProps } from 'components/Logs/AddToQueryHOC';
import { ActionItemProps } from 'container/LogDetailedView/ActionItem';
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
import { VIEWS } from './constants';
@@ -9,6 +10,7 @@ export type LogDetailProps = {
log: ILog | null;
selectedTab: VIEWS;
isListViewPanel?: boolean;
listViewPanelSelectedFields?: IField[] | null;
} & Pick<AddToQueryHOCProps, 'onAddToQuery'> &
Partial<Pick<ActionItemProps, 'onClickActionItem'>> &
Pick<DrawerProps, 'onClose'>;

View File

@@ -52,7 +52,7 @@
.log-body {
font-family: 'SF Mono';
font-family: 'Space Mono', monospace;
font-family: 'Geist Mono';
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);

View File

@@ -6,10 +6,13 @@ import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
import { RadioChangeEvent } from 'antd/lib';
import cx from 'classnames';
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
import { LOCALSTORAGE } from 'constants/localStorage';
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
import JSONView from 'container/LogDetailedView/JsonView';
import Overview from 'container/LogDetailedView/Overview';
import { aggregateAttributesResourcesToString } from 'container/LogDetailedView/utils';
import { useOptionsMenu } from 'container/OptionsMenu';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import {
@@ -21,9 +24,10 @@ import {
TextSelect,
X,
} from 'lucide-react';
import { useState } from 'react';
import { useMemo, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { VIEW_TYPES, VIEWS } from './constants';
import { LogDetailProps } from './LogDetail.interfaces';
@@ -36,6 +40,7 @@ function LogDetail({
onClickActionItem,
selectedTab,
isListViewPanel = false,
listViewPanelSelectedFields,
}: LogDetailProps): JSX.Element {
const [, copyToClipboard] = useCopyToClipboard();
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
@@ -45,6 +50,19 @@ function LogDetail({
const [contextQuery, setContextQuery] = useState<Query | undefined>();
const [filters, setFilters] = useState<TagFilter | null>(null);
const [isEdit, setIsEdit] = useState<boolean>(false);
const { initialDataSource, stagedQuery } = useQueryBuilder();
const listQuery = useMemo(() => {
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) return null;
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
}, [stagedQuery]);
const { options } = useOptionsMenu({
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
dataSource: initialDataSource || DataSource.LOGS,
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
});
const isDarkMode = useIsDarkMode();
@@ -192,6 +210,8 @@ function LogDetail({
onAddToQuery={onAddToQuery}
onClickActionItem={onClickActionItem}
isListViewPanel={isListViewPanel}
selectedOptions={options}
listViewPanelSelectedFields={listViewPanelSelectedFields}
/>
)}
{selectedView === VIEW_TYPES.JSON && <JSONView logData={log} />}

View File

@@ -2,7 +2,7 @@ import './AddToQueryHOC.styles.scss';
import { Popover } from 'antd';
import { OPERATORS } from 'constants/queryBuilder';
import { memo, ReactNode, useCallback, useMemo } from 'react';
import { memo, MouseEvent, ReactNode, useMemo } from 'react';
function AddToQueryHOC({
fieldKey,
@@ -10,9 +10,10 @@ function AddToQueryHOC({
onAddToQuery,
children,
}: AddToQueryHOCProps): JSX.Element {
const handleQueryAdd = useCallback(() => {
const handleQueryAdd = (event: MouseEvent<HTMLDivElement>): void => {
event.stopPropagation();
onAddToQuery(fieldKey, fieldValue, OPERATORS.IN);
}, [fieldKey, fieldValue, onAddToQuery]);
};
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
fieldKey,

View File

@@ -28,12 +28,17 @@ export const SEVERITY_TEXT_TYPE = {
FATAL2: 'FATAL2',
FATAL3: 'FATAL3',
FATAL4: 'FATAL4',
UNKNOWN: 'UNKNOWN',
} as const;
export const LogType = {
TRACE: 'TRACE',
DEBUG: 'DEBUG',
INFO: 'INFO',
WARNING: 'WARNING',
WARN: 'WARN',
ERROR: 'ERROR',
FATAL: 'FATAL',
UNKNOWN: 'UNKNOWN',
} as const;
function LogStateIndicator({

View File

@@ -1,9 +1,10 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { ILog } from 'types/api/logs/log';
import { getLogIndicatorType, getLogIndicatorTypeForTable } from './utils';
describe('getLogIndicatorType', () => {
it('should return severity type for valid log with severityText', () => {
it('severity_number should be given priority over severity_text', () => {
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
@@ -20,11 +21,57 @@ describe('getLogIndicatorType', () => {
attributesInt: {},
attributesFloat: {},
severity_text: 'INFO',
severity_number: 2,
};
expect(getLogIndicatorType(log)).toBe('INFO');
// severity_number should get priority over severity_text
expect(getLogIndicatorType(log)).toBe('TRACE');
});
it('should return log level if severityText is missing', () => {
it('severity_text should be used when severity_number is absent ', () => {
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
id: '123456',
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityText: 'INFO',
severityNumber: 2,
body: 'Sample log Message',
resources_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: 'FATAL',
severity_number: 0,
};
expect(getLogIndicatorType(log)).toBe('FATAL');
});
it('case insensitive severity_text should be valid', () => {
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
id: '123456',
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityText: 'INFO',
severityNumber: 2,
body: 'Sample log Message',
resources_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: 'fatAl',
severity_number: 0,
};
expect(getLogIndicatorType(log)).toBe('FATAL');
});
it('should return log level if severityText and severityNumber is missing', () => {
const log: ILog = {
date: '2024-02-29T12:34:58Z',
timestamp: 1646115296,
@@ -36,13 +83,16 @@ describe('getLogIndicatorType', () => {
body: 'Sample log',
resources_string: {},
attributesString: {},
attributes_string: {},
attributes_string: {
log_level: 'INFO' as never,
},
attributesInt: {},
attributesFloat: {},
severity_text: 'FATAL',
severity_text: 'some_random',
severityText: '',
severity_number: 0,
};
expect(getLogIndicatorType(log)).toBe('FATAL');
expect(getLogIndicatorType(log)).toBe('INFO');
});
});
@@ -55,6 +105,7 @@ describe('getLogIndicatorTypeForTable', () => {
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityNumber: 2,
severity_number: 2,
body: 'Sample log message',
resources_string: {},
@@ -64,7 +115,7 @@ describe('getLogIndicatorTypeForTable', () => {
attributesFloat: {},
severity_text: 'WARN',
};
expect(getLogIndicatorTypeForTable(log)).toBe('WARN');
expect(getLogIndicatorTypeForTable(log)).toBe('TRACE');
});
it('should return log level if severityText is missing', () => {
@@ -75,7 +126,8 @@ describe('getLogIndicatorTypeForTable', () => {
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityNumber: 2,
severityNumber: 0,
severity_number: 0,
body: 'Sample log message',
resources_string: {},
attributesString: {},
@@ -87,3 +139,47 @@ describe('getLogIndicatorTypeForTable', () => {
expect(getLogIndicatorTypeForTable(log)).toBe('INFO');
});
});
describe('logIndicatorBySeverityNumber', () => {
// https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
const logLevelExpectations = [
{ minSevNumber: 1, maxSevNumber: 4, expectedIndicatorType: 'TRACE' },
{ minSevNumber: 5, maxSevNumber: 8, expectedIndicatorType: 'DEBUG' },
{ minSevNumber: 9, maxSevNumber: 12, expectedIndicatorType: 'INFO' },
{ minSevNumber: 13, maxSevNumber: 16, expectedIndicatorType: 'WARN' },
{ minSevNumber: 17, maxSevNumber: 20, expectedIndicatorType: 'ERROR' },
{ minSevNumber: 21, maxSevNumber: 24, expectedIndicatorType: 'FATAL' },
];
logLevelExpectations.forEach((e) => {
for (let sevNum = e.minSevNumber; sevNum <= e.maxSevNumber; sevNum++) {
const sevText = (Math.random() + 1).toString(36).substring(2);
const log = {
date: '2024-02-29T12:34:46Z',
timestamp: 1646115296,
id: '123456',
traceId: '987654',
spanId: '54321',
traceFlags: 0,
severityText: sevText,
severityNumber: sevNum,
body: 'Sample log Message',
resources_string: {},
attributesString: {},
attributes_string: {},
attributesInt: {},
attributesFloat: {},
severity_text: sevText,
severity_number: sevNum,
};
it(`getLogIndicatorType should return ${e.expectedIndicatorType} for severity_text: ${sevText} and severity_number: ${sevNum}`, () => {
expect(getLogIndicatorType(log)).toBe(e.expectedIndicatorType);
});
it(`getLogIndicatorTypeForTable should return ${e.expectedIndicatorType} for severity_text: ${sevText} and severity_number: ${sevNum}`, () => {
expect(getLogIndicatorTypeForTable(log)).toBe(e.expectedIndicatorType);
});
}
});
});

View File

@@ -2,56 +2,112 @@ import { ILog } from 'types/api/logs/log';
import { LogType, SEVERITY_TEXT_TYPE } from './LogStateIndicator';
const getSeverityType = (severityText: string): string => {
const getLogTypeBySeverityText = (severityText: string): string => {
switch (severityText) {
case SEVERITY_TEXT_TYPE.TRACE:
case SEVERITY_TEXT_TYPE.TRACE2:
case SEVERITY_TEXT_TYPE.TRACE3:
case SEVERITY_TEXT_TYPE.TRACE4:
return SEVERITY_TEXT_TYPE.TRACE;
return LogType.TRACE;
case SEVERITY_TEXT_TYPE.DEBUG:
case SEVERITY_TEXT_TYPE.DEBUG2:
case SEVERITY_TEXT_TYPE.DEBUG3:
case SEVERITY_TEXT_TYPE.DEBUG4:
return SEVERITY_TEXT_TYPE.DEBUG;
return LogType.DEBUG;
case SEVERITY_TEXT_TYPE.INFO:
case SEVERITY_TEXT_TYPE.INFO2:
case SEVERITY_TEXT_TYPE.INFO3:
case SEVERITY_TEXT_TYPE.INFO4:
return SEVERITY_TEXT_TYPE.INFO;
return LogType.INFO;
case SEVERITY_TEXT_TYPE.WARN:
case SEVERITY_TEXT_TYPE.WARN2:
case SEVERITY_TEXT_TYPE.WARN3:
case SEVERITY_TEXT_TYPE.WARN4:
case SEVERITY_TEXT_TYPE.WARNING:
return SEVERITY_TEXT_TYPE.WARN;
return LogType.WARN;
case SEVERITY_TEXT_TYPE.ERROR:
case SEVERITY_TEXT_TYPE.ERROR2:
case SEVERITY_TEXT_TYPE.ERROR3:
case SEVERITY_TEXT_TYPE.ERROR4:
return SEVERITY_TEXT_TYPE.ERROR;
return LogType.ERROR;
case SEVERITY_TEXT_TYPE.FATAL:
case SEVERITY_TEXT_TYPE.FATAL2:
case SEVERITY_TEXT_TYPE.FATAL3:
case SEVERITY_TEXT_TYPE.FATAL4:
return SEVERITY_TEXT_TYPE.FATAL;
return LogType.FATAL;
default:
return SEVERITY_TEXT_TYPE.INFO;
return LogType.UNKNOWN;
}
};
export const getLogIndicatorType = (logData: ILog): string => {
if (logData.severity_text) {
return getSeverityType(logData.severity_text);
// https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
const getLogTypeBySeverityNumber = (severityNumber: number): string => {
if (severityNumber < 1) {
return LogType.UNKNOWN;
}
return logData.attributes_string?.log_level || LogType.INFO;
if (severityNumber < 5) {
return LogType.TRACE;
}
if (severityNumber < 9) {
return LogType.DEBUG;
}
if (severityNumber < 13) {
return LogType.INFO;
}
if (severityNumber < 17) {
return LogType.WARN;
}
if (severityNumber < 21) {
return LogType.ERROR;
}
if (severityNumber < 25) {
return LogType.FATAL;
}
return LogType.UNKNOWN;
};
const getLogType = (
severityText: string,
severityNumber: number,
defaultType: string,
): string => {
// give priority to the severityNumber
if (severityNumber) {
const logType = getLogTypeBySeverityNumber(severityNumber);
if (logType !== LogType.UNKNOWN) {
return logType;
}
}
// is severityNumber is not present then rely on the severityText
if (severityText) {
const logType = getLogTypeBySeverityText(severityText);
if (logType !== LogType.UNKNOWN) {
return logType;
}
}
return defaultType;
};
export const getLogIndicatorType = (logData: ILog): string => {
const defaultType = logData.attributes_string?.log_level || LogType.INFO;
// convert the severity_text to upper case for the comparison to support case insensitive values
return getLogType(
logData?.severity_text?.toUpperCase(),
logData?.severity_number || 0,
defaultType,
);
};
export const getLogIndicatorTypeForTable = (
log: Record<string, unknown>,
): string => {
if (log.severity_text) {
return getSeverityType(log.severity_text as string);
}
return (log.log_level as string) || LogType.INFO;
const defaultType = (log.log_level as string) || LogType.INFO;
// convert the severity_text to upper case for the comparison to support case insensitive values
return getLogType(
(log?.severity_text as string)?.toUpperCase(),
(log?.severity_number as number) || 0,
defaultType,
);
};

View File

@@ -62,8 +62,6 @@ function RawLogView({
const isDarkMode = useIsDarkMode();
const isReadOnlyLog = !isLogsExplorerPage || isReadOnly;
const severityText = data.severity_text ? `${data.severity_text} |` : '';
const logType = getLogIndicatorType(data);
const updatedSelecedFields = useMemo(
@@ -88,17 +86,16 @@ function RawLogView({
attributesText += ' | ';
}
const text = useMemo(
() =>
const text = useMemo(() => {
const date =
typeof data.timestamp === 'string'
? `${dayjs(data.timestamp).format(
'YYYY-MM-DD HH:mm:ss.SSS',
)} | ${attributesText} ${severityText} ${data.body}`
: `${dayjs(data.timestamp / 1e6).format(
'YYYY-MM-DD HH:mm:ss.SSS',
)} | ${attributesText} ${severityText} ${data.body}`,
[data.timestamp, data.body, severityText, attributesText],
);
? dayjs(data.timestamp)
: dayjs(data.timestamp / 1e6);
return `${date.format('YYYY-MM-DD HH:mm:ss.SSS')} | ${attributesText} ${
data.body
}`;
}, [data.timestamp, data.body, attributesText]);
const handleClickExpand = useCallback(() => {
if (activeContextLog || isReadOnly) return;

View File

@@ -49,7 +49,7 @@ export const ExpandIconWrapper = styled(Col)`
export const RawLogContent = styled.div<RawLogContentProps>`
margin-bottom: 0;
font-family: 'SF Mono', monospace;
font-family: 'Space Mono', monospace;
font-family: 'Geist Mono';
font-size: 13px;
font-weight: 400;
text-align: left;

View File

@@ -0,0 +1,54 @@
import TypicalOverlayScrollbar from 'components/TypicalOverlayScrollbar/TypicalOverlayScrollbar';
import VirtuosoOverlayScrollbar from 'components/VirtuosoOverlayScrollbar/VirtuosoOverlayScrollbar';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { PartialOptions } from 'overlayscrollbars';
import { CSSProperties, ReactElement, useMemo } from 'react';
type Props = {
children: ReactElement;
isVirtuoso?: boolean;
style?: CSSProperties;
options?: PartialOptions;
};
function OverlayScrollbar({
children,
isVirtuoso,
style,
options: customOptions,
}: Props): any {
const isDarkMode = useIsDarkMode();
const options = useMemo(
() =>
({
scrollbars: {
autoHide: 'scroll',
theme: isDarkMode ? 'os-theme-light' : 'os-theme-dark',
},
...(customOptions || {}),
} as PartialOptions),
[customOptions, isDarkMode],
);
if (isVirtuoso) {
return (
<VirtuosoOverlayScrollbar style={style} options={options}>
{children}
</VirtuosoOverlayScrollbar>
);
}
return (
<TypicalOverlayScrollbar style={style} options={options}>
{children}
</TypicalOverlayScrollbar>
);
}
OverlayScrollbar.defaultProps = {
isVirtuoso: false,
style: {},
options: {},
};
export default OverlayScrollbar;

View File

@@ -2,7 +2,9 @@
import './DynamicColumnTable.syles.scss';
import { Button, Dropdown, Flex, MenuProps, Switch } from 'antd';
import { ColumnGroupType, ColumnType } from 'antd/es/table';
import { ColumnsType } from 'antd/lib/table';
import logEvent from 'api/common/logEvent';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { SlidersHorizontal } from 'lucide-react';
import { memo, useEffect, useState } from 'react';
@@ -22,6 +24,7 @@ function DynamicColumnTable({
dynamicColumns,
onDragColumn,
facingIssueBtn,
shouldSendAlertsLogEvent,
...restProps
}: DynamicColumnTableProps): JSX.Element {
const [columnsData, setColumnsData] = useState<ColumnsType | undefined>(
@@ -47,11 +50,18 @@ function DynamicColumnTable({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [columns, dynamicColumns]);
const onToggleHandler = (index: number) => (
checked: boolean,
event: React.MouseEvent<HTMLButtonElement>,
): void => {
const onToggleHandler = (
index: number,
column: ColumnGroupType<any> | ColumnType<any>,
) => (checked: boolean, event: React.MouseEvent<HTMLButtonElement>): void => {
event.stopPropagation();
if (shouldSendAlertsLogEvent) {
logEvent('Alert: Column toggled', {
column: column?.title,
action: checked ? 'Enable' : 'Disable',
});
}
setVisibleColumns({
tablesource,
dynamicColumns,
@@ -75,7 +85,7 @@ function DynamicColumnTable({
<div>{column.title?.toString()}</div>
<Switch
checked={columnsData?.findIndex((c) => c.key === column.key) !== -1}
onChange={onToggleHandler(index)}
onChange={onToggleHandler(index, column)}
/>
</div>
),

View File

@@ -3,6 +3,7 @@
import { Table } from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { dragColumnParams } from 'hooks/useDragColumns/configs';
import { set } from 'lodash-es';
import {
SyntheticEvent,
useCallback,
@@ -20,6 +21,7 @@ import { ResizeTableProps } from './types';
function ResizeTable({
columns,
onDragColumn,
pagination,
...restProps
}: ResizeTableProps): JSX.Element {
const [columnsData, setColumns] = useState<ColumnsType>([]);
@@ -58,14 +60,21 @@ function ResizeTable({
[columnsData, onDragColumn, handleResize],
);
const tableParams = useMemo(
() => ({
const tableParams = useMemo(() => {
const props = {
...restProps,
components: { header: { cell: ResizableHeader } },
columns: mergedColumns,
}),
[mergedColumns, restProps],
);
};
set(
props,
'pagination',
pagination ? { ...pagination, hideOnSinglePage: true } : false,
);
return props;
}, [mergedColumns, pagination, restProps]);
useEffect(() => {
if (columns) {

View File

@@ -14,6 +14,7 @@ export interface DynamicColumnTableProps extends TableProps<any> {
dynamicColumns: TableProps<any>['columns'];
onDragColumn?: (fromIndex: number, toIndex: number) => void;
facingIssueBtn?: FacingIssueBtnProps;
shouldSendAlertsLogEvent?: boolean;
}
export type GetVisibleColumnsFunction = (

View File

@@ -5,7 +5,6 @@ import { Button } from 'antd';
import { Tag } from 'antd/lib';
import Input from 'components/Input';
import { Check, X } from 'lucide-react';
import { TweenOneGroup } from 'rc-tween-one';
import React, { Dispatch, SetStateAction, useState } from 'react';
function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
@@ -46,41 +45,19 @@ function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
func(value);
};
const forMap = (tag: string): React.ReactElement => (
<span key={tag} style={{ display: 'inline-block' }}>
<Tag
closable
onClose={(e): void => {
e.preventDefault();
handleClose(tag);
}}
>
{tag}
</Tag>
</span>
);
const tagChild = tags.map(forMap);
const renderTagsAnimated = (): React.ReactElement => (
<TweenOneGroup
appear={false}
className="tags"
enter={{ scale: 0.8, opacity: 0, type: 'from', duration: 100 }}
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
onEnd={(e): void => {
if (e.type === 'appear' || e.type === 'enter') {
(e.target as any).style = 'display: inline-block';
}
}}
>
{tagChild}
</TweenOneGroup>
);
return (
<div className="tags-container">
{renderTagsAnimated()}
{tags.map<React.ReactNode>((tag) => (
<Tag
key={tag}
closable
style={{ userSelect: 'none' }}
onClose={(): void => handleClose(tag)}
>
<span>{tag}</span>
</Tag>
))}
{inputVisible && (
<div className="add-tag-container">
<Input

View File

@@ -9,7 +9,6 @@ import { Tooltip } from 'antd';
import { themeColors } from 'constants/theme';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useMemo } from 'react';
import { popupContainer } from 'utils/selectPopupContainer';
import { style } from './constant';
@@ -64,7 +63,7 @@ function TextToolTip({
);
return (
<Tooltip getTooltipContainer={popupContainer} overlay={overlay}>
<Tooltip overlay={overlay}>
{useFilledIcon ? (
<QuestionCircleFilled style={iconStyle} />
) : (

View File

@@ -0,0 +1,31 @@
import './typicalOverlayScrollbar.scss';
import { PartialOptions } from 'overlayscrollbars';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import { CSSProperties, ReactElement } from 'react';
interface Props {
children: ReactElement;
style?: CSSProperties;
options?: PartialOptions;
}
export default function TypicalOverlayScrollbar({
children,
style,
options,
}: Props): ReturnType<typeof OverlayScrollbarsComponent> {
return (
<OverlayScrollbarsComponent
defer
options={options}
style={style}
className="overlay-scrollbar"
data-overlayscrollbars-initialize
>
{children}
</OverlayScrollbarsComponent>
);
}
TypicalOverlayScrollbar.defaultProps = { style: {}, options: {} };

View File

@@ -0,0 +1,3 @@
.overlay-scrollbar {
height: 100%;
}

View File

@@ -49,7 +49,10 @@ function ValueGraph({
}
>
<Tooltip title={t('this_value_satisfies_multiple_thresholds')}>
<ExclamationCircleFilled className="value-graph-icon" />
<ExclamationCircleFilled
className="value-graph-icon"
data-testid="conflicting-thresholds"
/>
</Tooltip>
</div>
)}

View File

@@ -0,0 +1,37 @@
import './virtuosoOverlayScrollbar.scss';
import useInitializeOverlayScrollbar from 'hooks/useInitializeOverlayScrollbar/useInitializeOverlayScrollbar';
import { PartialOptions } from 'overlayscrollbars';
import React, { CSSProperties, ReactElement } from 'react';
interface VirtuosoOverlayScrollbarProps {
children: ReactElement;
style?: CSSProperties;
options: PartialOptions;
}
export default function VirtuosoOverlayScrollbar({
children,
style,
options,
}: VirtuosoOverlayScrollbarProps): JSX.Element {
const { rootRef, setScroller } = useInitializeOverlayScrollbar(options);
const enhancedChild = React.cloneElement(children, {
scrollerRef: setScroller,
'data-overlayscrollbars-initialize': true,
});
return (
<div
data-overlayscrollbars-initialize
ref={rootRef}
className="overlay-scroll-wrapper"
style={style}
>
{enhancedChild}
</div>
);
}
VirtuosoOverlayScrollbar.defaultProps = { style: {} };

View File

@@ -0,0 +1,5 @@
.overlay-scroll-wrapper {
height: 100%;
width: 100%;
overflow: auto;
}

View File

@@ -16,6 +16,7 @@ export interface FacingIssueBtnProps {
buttonText?: string;
className?: string;
onHoverText?: string;
intercomMessageDisabled?: boolean;
}
function FacingIssueBtn({
@@ -25,11 +26,12 @@ function FacingIssueBtn({
buttonText = '',
className = '',
onHoverText = '',
intercomMessageDisabled = false,
}: FacingIssueBtnProps): JSX.Element | null {
const handleFacingIssuesClick = (): void => {
logEvent(eventName, attributes);
if (window.Intercom) {
if (window.Intercom && !intercomMessageDisabled) {
window.Intercom('showNewMessage', defaultTo(message, ''));
}
};
@@ -62,6 +64,7 @@ FacingIssueBtn.defaultProps = {
buttonText: '',
className: '',
onHoverText: '',
intercomMessageDisabled: false,
};
export default FacingIssueBtn;

View File

@@ -19,6 +19,5 @@ export enum FeatureKeys {
OSS = 'OSS',
ONBOARDING = 'ONBOARDING',
CHAT_SUPPORT = 'CHAT_SUPPORT',
PLANNED_MAINTENANCE = 'PLANNED_MAINTENANCE',
GATEWAY = 'GATEWAY',
}

View File

@@ -23,6 +23,10 @@ export const metricQueryFunctionOptions: SelectOption<string, string>[] = [
value: QueryFunctionsTypes.ABSOLUTE,
label: 'Absolute',
},
{
value: QueryFunctionsTypes.RUNNING_DIFF,
label: 'Running Diff',
},
{
value: QueryFunctionsTypes.LOG_2,
label: 'Log2',
@@ -103,6 +107,9 @@ export const queryFunctionsTypesConfig: QueryFunctionConfigType = {
absolute: {
showInput: false,
},
runningDiff: {
showInput: false,
},
log2: {
showInput: false,
},

View File

@@ -13,6 +13,7 @@ const ROUTES = {
GET_STARTED_INFRASTRUCTURE_MONITORING:
'/get-started/infrastructure-monitoring',
GET_STARTED_AWS_MONITORING: '/get-started/aws-monitoring',
GET_STARTED_AZURE_MONITORING: '/get-started/azure-monitoring',
USAGE_EXPLORER: '/usage-explorer',
APPLICATION: '/services',
ALL_DASHBOARD: '/dashboard',

View File

@@ -0,0 +1,78 @@
import AlertChannels from 'container/AllAlertChannels';
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
jest.mock('hooks/useFetch', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
payload: allAlertChannels,
})),
}));
const successNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
useNotifications: jest.fn(() => ({
notifications: {
success: successNotification,
error: jest.fn(),
},
})),
}));
describe('Alert Channels Settings List page', () => {
beforeEach(() => {
render(<AlertChannels />);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Should display the Alert Channels page properly', () => {
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
expect(screen.getByText('sending_channels_note')).toBeInTheDocument();
});
it('Should check if "New Alert Channel" Button is visble ', () => {
expect(screen.getByText('button_new_channel')).toBeInTheDocument();
});
it('Should check if the help icon is visible and displays "tooltip_notification_channels ', async () => {
const helpIcon = screen.getByLabelText('question-circle');
fireEvent.mouseOver(helpIcon);
await waitFor(() => {
const tooltip = screen.getByText('tooltip_notification_channels');
expect(tooltip).toBeInTheDocument();
});
});
});
describe('Should check if the channels table is properly displayed', () => {
it('Should check if the table columns are properly displayed', () => {
expect(screen.getByText('column_channel_name')).toBeInTheDocument();
expect(screen.getByText('column_channel_type')).toBeInTheDocument();
expect(screen.getByText('column_channel_action')).toBeInTheDocument();
});
it('Should check if the data in the table is displayed properly', () => {
expect(screen.getByText('Dummy-Channel')).toBeInTheDocument();
expect(screen.getAllByText('slack')[0]).toBeInTheDocument();
expect(screen.getAllByText('column_channel_edit')[0]).toBeInTheDocument();
expect(screen.getAllByText('Delete')[0]).toBeInTheDocument();
});
it('Should check if clicking on Delete displays Success Toast "Channel Deleted Successfully"', async () => {
const deleteButton = screen.getAllByRole('button', { name: 'Delete' })[0];
expect(deleteButton).toBeInTheDocument();
act(() => {
fireEvent.click(deleteButton);
});
await waitFor(() => {
expect(successNotification).toBeCalledWith({
message: 'Success',
description: 'channel_delete_success',
});
});
});
});
});

View File

@@ -0,0 +1,72 @@
import AlertChannels from 'container/AllAlertChannels';
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
jest.mock('hooks/useFetch', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
payload: allAlertChannels,
})),
}));
const successNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
useNotifications: jest.fn(() => ({
notifications: {
success: successNotification,
error: jest.fn(),
},
})),
}));
jest.mock('hooks/useComponentPermission', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => [false]),
}));
describe('Alert Channels Settings List page (Normal User)', () => {
beforeEach(() => {
render(<AlertChannels />);
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Should display the Alert Channels page properly', () => {
it('Should check if "The alerts will be sent to all the configured channels." is visible ', () => {
expect(screen.getByText('sending_channels_note')).toBeInTheDocument();
});
it('Should check if "New Alert Channel" Button is visble and disabled', () => {
const newAlertButton = screen.getByRole('button', {
name: 'plus button_new_channel',
});
expect(newAlertButton).toBeInTheDocument();
expect(newAlertButton).toBeDisabled();
});
it('Should check if the help icon is visible and displays "tooltip_notification_channels ', async () => {
const helpIcon = screen.getByLabelText('question-circle');
fireEvent.mouseOver(helpIcon);
await waitFor(() => {
const tooltip = screen.getByText('tooltip_notification_channels');
expect(tooltip).toBeInTheDocument();
});
});
});
describe('Should check if the channels table is properly displayed', () => {
it('Should check if the table columns are properly displayed', () => {
expect(screen.getByText('column_channel_name')).toBeInTheDocument();
expect(screen.getByText('column_channel_type')).toBeInTheDocument();
expect(screen.queryByText('column_channel_action')).not.toBeInTheDocument();
});
it('Should check if the data in the table is displayed properly', () => {
expect(screen.getByText('Dummy-Channel')).toBeInTheDocument();
expect(screen.getAllByText('slack')[0]).toBeInTheDocument();
expect(screen.queryByText('column_channel_edit')).not.toBeInTheDocument();
expect(screen.queryByText('Delete')).not.toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,424 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/no-identical-functions */
import CreateAlertChannels from 'container/CreateAlertChannels';
import { ChannelType } from 'container/CreateAlertChannels/config';
import {
opsGenieDescriptionDefaultValue,
opsGenieMessageDefaultValue,
opsGeniePriorityDefaultValue,
pagerDutyAdditionalDetailsDefaultValue,
pagerDutyDescriptionDefaultVaule,
pagerDutySeverityTextDefaultValue,
slackDescriptionDefaultValue,
slackTitleDefaultValue,
} from 'mocks-server/__mockdata__/alerts';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
import { testLabelInputAndHelpValue } from './testUtils';
const successNotification = jest.fn();
const errorNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
useNotifications: jest.fn(() => ({
notifications: {
success: successNotification,
error: errorNotification,
},
})),
}));
jest.mock('hooks/useFeatureFlag', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
active: true,
})),
}));
describe('Create Alert Channel', () => {
afterEach(() => {
jest.clearAllMocks();
});
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel ', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Slack} />);
});
afterEach(() => {
jest.clearAllMocks();
});
it('Should check if the title is "New Notification Channels"', () => {
expect(screen.getByText('page_title_create')).toBeInTheDocument();
});
it('Should check if the name label and textbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_name',
testId: 'channel-name-textbox',
});
});
it('Should check if Send resolved alerts label and checkbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_send_resolved',
testId: 'field-send-resolved-checkbox',
});
});
it('Should check if channel type label and dropdown are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_type',
testId: 'channel-type-select',
});
});
// Default Channel type (Slack) fields
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
expect(screen.getByText('Slack')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_recipient',
testId: 'slack-channel-textbox',
helpText: 'slack_channel_help',
});
});
it('Should check if Title label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_title',
testId: 'title-textarea',
});
});
it('Should check if Title contains template', () => {
const titleTextArea = screen.getByTestId('title-textarea');
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
});
it('Should check if Description label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_description',
testId: 'description-textarea',
});
});
it('Should check if Description contains template', () => {
const descriptionTextArea = screen.getByTestId('description-textarea');
expect(descriptionTextArea).toHaveTextContent(slackDescriptionDefaultValue);
});
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
expect(screen.getByText('button_save_channel')).toBeInTheDocument();
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 () => {
const saveButton = screen.getByRole('button', {
name: 'button_save_channel',
});
fireEvent.click(saveButton);
await waitFor(() =>
expect(errorNotification).toHaveBeenCalledWith({
description: 'Something went wrong',
message: 'Error',
}),
);
});
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(
rest.post('http://localhost/api/v1/testChannel', (req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
status: 'success',
data: 'test alert sent',
}),
),
),
);
const testButton = screen.getByRole('button', {
name: 'button_test_channel',
});
fireEvent.click(testButton);
await waitFor(() =>
expect(successNotification).toHaveBeenCalledWith({
message: 'Success',
description: 'channel_test_done',
}),
);
});
it('Should check if clicking on Test button shows "Something went wrong" error message if testing fails', async () => {
const testButton = screen.getByRole('button', {
name: 'button_test_channel',
});
fireEvent.click(testButton);
await waitFor(() =>
expect(errorNotification).toHaveBeenCalledWith({
message: 'Error',
description: 'channel_test_failed',
}),
);
});
});
describe('New Alert Channel Cascading Fields Based on Channel Type', () => {
describe('Webhook', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Webhook} />);
});
it('Should check if the selected item in the type dropdown has text "Webhook"', () => {
expect(screen.getByText('Webhook')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Webhook User Name label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_username',
testId: 'webhook-username-textbox',
helpText: 'help_webhook_username',
});
});
it('Should check if Password label and textbox, and help text are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'Password (optional)',
testId: 'webhook-password-textbox',
helpText: 'help_webhook_password',
});
});
});
describe('PagerDuty', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Pagerduty} />);
});
it('Should check if the selected item in the type dropdown has text "Pagerduty"', () => {
expect(screen.getByText('Pagerduty')).toBeInTheDocument();
});
it('Should check if Routing key label, required, and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_routing_key',
testId: 'pager-routing-key-textbox',
});
});
it('Should check if Description label, required, info (Shows up as description in pagerduty), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_description',
testId: 'pager-description-textarea',
helpText: 'help_pager_description',
});
});
it('Should check if the description contains default template', () => {
const descriptionTextArea = screen.getByTestId(
'pager-description-textarea',
);
expect(descriptionTextArea).toHaveTextContent(
pagerDutyDescriptionDefaultVaule,
);
});
it('Should check if Severity label, info (help_pager_severity), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_severity',
testId: 'pager-severity-textbox',
helpText: 'help_pager_severity',
});
});
it('Should check if Severity contains the default template', () => {
const severityTextbox = screen.getByTestId('pager-severity-textbox');
expect(severityTextbox).toHaveValue(pagerDutySeverityTextDefaultValue);
});
it('Should check if Additional Information label, text area, and help text (help_pager_details) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_details',
testId: 'pager-additional-details-textarea',
helpText: 'help_pager_details',
});
});
it('Should check if Additional Information contains the default template', () => {
const detailsTextArea = screen.getByTestId(
'pager-additional-details-textarea',
);
expect(detailsTextArea).toHaveValue(pagerDutyAdditionalDetailsDefaultValue);
});
it('Should check if Group label, text area, and info (help_pager_group) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_group',
testId: 'pager-group-textarea',
helpText: 'help_pager_group',
});
});
it('Should check if Class label, text area, and info (help_pager_class) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_class',
testId: 'pager-class-textarea',
helpText: 'help_pager_class',
});
});
it('Should check if Client label, text area, and info (Shows up as event source in Pagerduty) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_client',
testId: 'pager-client-textarea',
helpText: 'help_pager_client',
});
});
it('Should check if Client input contains the default value "SigNoz Alert Manager"', () => {
const clientTextArea = screen.getByTestId('pager-client-textarea');
expect(clientTextArea).toHaveValue('SigNoz Alert Manager');
});
it('Should check if Client URL label, text area, and info (Shows up as event source link in Pagerduty) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_client_url',
testId: 'pager-client-url-textarea',
helpText: 'help_pager_client_url',
});
});
it('Should check if Client URL contains the default value "https://enter-signoz-host-n-port-here/alerts"', () => {
const clientUrlTextArea = screen.getByTestId('pager-client-url-textarea');
expect(clientUrlTextArea).toHaveValue(
'https://enter-signoz-host-n-port-here/alerts',
);
});
});
describe('Opsgenie', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Opsgenie} />);
});
it('Should check if the selected item in the type dropdown has text "Opsgenie"', () => {
expect(screen.getByText('Opsgenie')).toBeInTheDocument();
});
it('Should check if API key label, required, and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_api_key',
testId: 'opsgenie-api-key-textbox',
required: true,
});
});
it('Should check if Message label, required, info (Shows up as message in opsgenie), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_message',
testId: 'opsgenie-message-textarea',
helpText: 'help_opsgenie_message',
required: true,
});
});
it('Should check if Message contains the default template ', () => {
const messageTextArea = screen.getByTestId('opsgenie-message-textarea');
expect(messageTextArea).toHaveValue(opsGenieMessageDefaultValue);
});
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_description',
testId: 'opsgenie-description-textarea',
helpText: 'help_opsgenie_description',
required: true,
});
});
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
const descriptionTextArea = screen.getByTestId(
'opsgenie-description-textarea',
);
expect(descriptionTextArea).toHaveTextContent(
opsGenieDescriptionDefaultValue,
);
});
it('Should check if Priority label, required, info (help_opsgenie_priority), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_priority',
testId: 'opsgenie-priority-textarea',
helpText: 'help_opsgenie_priority',
required: true,
});
});
it('Should check if Message contains the default template', () => {
const priorityTextArea = screen.getByTestId('opsgenie-priority-textarea');
expect(priorityTextArea).toHaveValue(opsGeniePriorityDefaultValue);
});
});
describe('Opsgenie', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Email} />);
});
it('Should check if the selected item in the type dropdown has text "Email"', () => {
expect(screen.getByText('Email')).toBeInTheDocument();
});
it('Should check if API key label, required, info(help_email_to), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_email_to',
testId: 'email-to-textbox',
helpText: 'help_email_to',
required: true,
});
});
});
describe('Microsoft Teams', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.MsTeams} />);
});
it('Should check if the selected item in the type dropdown has text "msteams"', () => {
expect(screen.getByText('msteams')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Title label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_title',
testId: 'title-textarea',
});
});
it('Should check if Title contains template', () => {
const titleTextArea = screen.getByTestId('title-textarea');
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
});
it('Should check if Description label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_description',
testId: 'description-textarea',
});
});
it('Should check if Description contains template', () => {
const descriptionTextArea = screen.getByTestId('description-textarea');
expect(descriptionTextArea).toHaveTextContent(slackDescriptionDefaultValue);
});
});
});
});

View File

@@ -0,0 +1,348 @@
/* eslint-disable sonarjs/no-duplicate-string */
/* eslint-disable sonarjs/no-identical-functions */
import { SIGNOZ_UPGRADE_PLAN_URL } from 'constants/app';
import CreateAlertChannels from 'container/CreateAlertChannels';
import { ChannelType } from 'container/CreateAlertChannels/config';
import {
opsGenieDescriptionDefaultValue,
opsGenieMessageDefaultValue,
opsGeniePriorityDefaultValue,
pagerDutyAdditionalDetailsDefaultValue,
pagerDutyDescriptionDefaultVaule,
pagerDutySeverityTextDefaultValue,
slackDescriptionDefaultValue,
slackTitleDefaultValue,
} from 'mocks-server/__mockdata__/alerts';
import { render, screen } from 'tests/test-utils';
import { testLabelInputAndHelpValue } from './testUtils';
describe('Create Alert Channel (Normal User)', () => {
afterEach(() => {
jest.clearAllMocks();
});
describe('Should check if the new alert channel is properly displayed with the cascading fields of slack channel ', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Slack} />);
});
it('Should check if the title is "New Notification Channels"', () => {
expect(screen.getByText('page_title_create')).toBeInTheDocument();
});
it('Should check if the name label and textbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_name',
testId: 'channel-name-textbox',
});
});
it('Should check if Send resolved alerts label and checkbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_send_resolved',
testId: 'field-send-resolved-checkbox',
});
});
it('Should check if channel type label and dropdown are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_type',
testId: 'channel-type-select',
});
});
// Default Channel type (Slack) fields
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
expect(screen.getByText('Slack')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_recipient',
testId: 'slack-channel-textbox',
helpText: 'slack_channel_help',
});
});
it('Should check if Title label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_title',
testId: 'title-textarea',
});
});
it('Should check if Title contains template', () => {
const titleTextArea = screen.getByTestId('title-textarea');
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
});
it('Should check if Description label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_description',
testId: 'description-textarea',
});
});
it('Should check if Description contains template', () => {
const descriptionTextArea = screen.getByTestId('description-textarea');
expect(descriptionTextArea).toHaveTextContent(slackDescriptionDefaultValue);
});
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
expect(screen.getByText('button_save_channel')).toBeInTheDocument();
expect(screen.getByText('button_test_channel')).toBeInTheDocument();
expect(screen.getByText('button_return')).toBeInTheDocument();
});
});
describe('New Alert Channel Cascading Fields Based on Channel Type', () => {
describe('Webhook', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Webhook} />);
});
it('Should check if the selected item in the type dropdown has text "Webhook"', () => {
expect(screen.getByText('Webhook')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
});
});
it('Should check if Webhook User Name label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_username',
testId: 'webhook-username-textbox',
helpText: 'help_webhook_username',
});
});
it('Should check if Password label and textbox, and help text are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'Password (optional)',
testId: 'webhook-password-textbox',
helpText: 'help_webhook_password',
});
});
});
describe('PagerDuty', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Pagerduty} />);
});
it('Should check if the selected item in the type dropdown has text "Pagerduty"', () => {
expect(screen.getByText('Pagerduty')).toBeInTheDocument();
});
it('Should check if Routing key label, required, and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_routing_key',
testId: 'pager-routing-key-textbox',
});
});
it('Should check if Description label, required, info (Shows up as description in pagerduty), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_description',
testId: 'pager-description-textarea',
helpText: 'help_pager_description',
});
});
it('Should check if the description contains default template', () => {
const descriptionTextArea = screen.getByTestId(
'pager-description-textarea',
);
expect(descriptionTextArea).toHaveTextContent(
pagerDutyDescriptionDefaultVaule,
);
});
it('Should check if Severity label, info (help_pager_severity), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_severity',
testId: 'pager-severity-textbox',
helpText: 'help_pager_severity',
});
});
it('Should check if Severity contains the default template', () => {
const severityTextbox = screen.getByTestId('pager-severity-textbox');
expect(severityTextbox).toHaveValue(pagerDutySeverityTextDefaultValue);
});
it('Should check if Additional Information label, text area, and help text (help_pager_details) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_details',
testId: 'pager-additional-details-textarea',
helpText: 'help_pager_details',
});
});
it('Should check if Additional Information contains the default template', () => {
const detailsTextArea = screen.getByTestId(
'pager-additional-details-textarea',
);
expect(detailsTextArea).toHaveValue(pagerDutyAdditionalDetailsDefaultValue);
});
it('Should check if Group label, text area, and info (help_pager_group) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_group',
testId: 'pager-group-textarea',
helpText: 'help_pager_group',
});
});
it('Should check if Class label, text area, and info (help_pager_class) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_class',
testId: 'pager-class-textarea',
helpText: 'help_pager_class',
});
});
it('Should check if Client label, text area, and info (Shows up as event source in Pagerduty) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_client',
testId: 'pager-client-textarea',
helpText: 'help_pager_client',
});
});
it('Should check if Client input contains the default value "SigNoz Alert Manager"', () => {
const clientTextArea = screen.getByTestId('pager-client-textarea');
expect(clientTextArea).toHaveValue('SigNoz Alert Manager');
});
it('Should check if Client URL label, text area, and info (Shows up as event source link in Pagerduty) are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_pager_client_url',
testId: 'pager-client-url-textarea',
helpText: 'help_pager_client_url',
});
});
it('Should check if Client URL contains the default value "https://enter-signoz-host-n-port-here/alerts"', () => {
const clientUrlTextArea = screen.getByTestId('pager-client-url-textarea');
expect(clientUrlTextArea).toHaveValue(
'https://enter-signoz-host-n-port-here/alerts',
);
});
});
describe('Opsgenie', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Opsgenie} />);
});
it('Should check if the selected item in the type dropdown has text "Opsgenie"', () => {
expect(screen.getByText('Opsgenie')).toBeInTheDocument();
});
it('Should check if API key label, required, and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_api_key',
testId: 'opsgenie-api-key-textbox',
required: true,
});
});
it('Should check if Message label, required, info (Shows up as message in opsgenie), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_message',
testId: 'opsgenie-message-textarea',
helpText: 'help_opsgenie_message',
required: true,
});
});
it('Should check if Message contains the default template ', () => {
const messageTextArea = screen.getByTestId('opsgenie-message-textarea');
expect(messageTextArea).toHaveValue(opsGenieMessageDefaultValue);
});
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_description',
testId: 'opsgenie-description-textarea',
helpText: 'help_opsgenie_description',
required: true,
});
});
it('Should check if Description label, required, info (Shows up as description in opsgenie), and text area are displayed properly `{{ if gt (len .Alerts.Firing) 0 -}}', () => {
const descriptionTextArea = screen.getByTestId(
'opsgenie-description-textarea',
);
expect(descriptionTextArea).toHaveTextContent(
opsGenieDescriptionDefaultValue,
);
});
it('Should check if Priority label, required, info (help_opsgenie_priority), and text area are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_opsgenie_priority',
testId: 'opsgenie-priority-textarea',
helpText: 'help_opsgenie_priority',
required: true,
});
});
it('Should check if Message contains the default template', () => {
const priorityTextArea = screen.getByTestId('opsgenie-priority-textarea');
expect(priorityTextArea).toHaveValue(opsGeniePriorityDefaultValue);
});
});
describe('Opsgenie', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.Email} />);
});
it('Should check if the selected item in the type dropdown has text "Email"', () => {
expect(screen.getByText('Email')).toBeInTheDocument();
});
it('Should check if API key label, required, info(help_email_to), and textbox are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_email_to',
testId: 'email-to-textbox',
helpText: 'help_email_to',
required: true,
});
});
});
describe('Microsoft Teams', () => {
beforeEach(() => {
render(<CreateAlertChannels preType={ChannelType.MsTeams} />);
});
it('Should check if the selected item in the type dropdown has text "Microsoft Teams (Supported in Paid Plans Only)"', () => {
expect(
screen.getByText('Microsoft Teams (Supported in Paid Plans Only)'),
).toBeInTheDocument();
});
it('Should check if the upgrade plan message is shown', () => {
expect(screen.getByText('Upgrade to a Paid Plan')).toBeInTheDocument();
expect(
screen.getByText(/This feature is available for paid plans only./),
).toBeInTheDocument();
const link = screen.getByRole('link', { name: 'Click here' });
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute('href', SIGNOZ_UPGRADE_PLAN_URL);
expect(screen.getByText(/to Upgrade/)).toBeInTheDocument();
});
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
expect(
screen.getByRole('button', { name: 'button_save_channel' }),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'button_test_channel' }),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'button_return' }),
).toBeInTheDocument();
});
it('Should check if save and test buttons are disabled', () => {
expect(
screen.getByRole('button', { name: 'button_save_channel' }),
).toBeDisabled();
expect(
screen.getByRole('button', { name: 'button_test_channel' }),
).toBeDisabled();
});
});
});
});

View File

@@ -0,0 +1,118 @@
import EditAlertChannels from 'container/EditAlertChannels';
import {
editAlertChannelInitialValue,
editSlackDescriptionDefaultValue,
slackTitleDefaultValue,
} from 'mocks-server/__mockdata__/alerts';
import { render, screen } from 'tests/test-utils';
import { testLabelInputAndHelpValue } from './testUtils';
const successNotification = jest.fn();
const errorNotification = jest.fn();
jest.mock('hooks/useNotifications', () => ({
__esModule: true,
useNotifications: jest.fn(() => ({
notifications: {
success: successNotification,
error: errorNotification,
},
})),
}));
jest.mock('hooks/useFeatureFlag', () => ({
__esModule: true,
default: jest.fn().mockImplementation(() => ({
active: true,
})),
}));
describe('Should check if the edit alert channel is properly displayed ', () => {
beforeEach(() => {
render(<EditAlertChannels initialValue={editAlertChannelInitialValue} />);
});
afterEach(() => {
jest.clearAllMocks();
});
it('Should check if the title is "Edit Notification Channels"', () => {
expect(screen.getByText('page_title_edit')).toBeInTheDocument();
});
it('Should check if the name label and textbox are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_name',
testId: 'channel-name-textbox',
value: 'Dummy-Channel',
});
});
it('Should check if Send resolved alerts label and checkbox are displayed properly and the checkbox is checked ', () => {
testLabelInputAndHelpValue({
labelText: 'field_send_resolved',
testId: 'field-send-resolved-checkbox',
});
expect(screen.getByTestId('field-send-resolved-checkbox')).toBeChecked();
});
it('Should check if channel type label and dropdown are displayed properly', () => {
testLabelInputAndHelpValue({
labelText: 'field_channel_type',
testId: 'channel-type-select',
});
});
it('Should check if the selected item in the type dropdown has text "Slack"', () => {
expect(screen.getByText('Slack')).toBeInTheDocument();
});
it('Should check if Webhook URL label and input are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_webhook_url',
testId: 'webhook-url-textbox',
value:
'https://discord.com/api/webhooks/dummy_webhook_id/dummy_webhook_token/slack',
});
});
it('Should check if Recepient label, input, and help text are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_recipient',
testId: 'slack-channel-textbox',
helpText: 'slack_channel_help',
value: '#dummy_channel',
});
});
it('Should check if Title label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_title',
testId: 'title-textarea',
});
});
it('Should check if Title contains template', () => {
const titleTextArea = screen.getByTestId('title-textarea');
expect(titleTextArea).toHaveTextContent(slackTitleDefaultValue);
});
it('Should check if Description label and text area are displayed properly ', () => {
testLabelInputAndHelpValue({
labelText: 'field_slack_description',
testId: 'description-textarea',
});
});
it('Should check if Description contains template', () => {
const descriptionTextArea = screen.getByTestId('description-textarea');
expect(descriptionTextArea).toHaveTextContent(
editSlackDescriptionDefaultValue,
);
});
it('Should check if the form buttons are displayed properly (Save, Test, Back)', () => {
expect(screen.getByText('button_save_channel')).toBeInTheDocument();
expect(screen.getByText('button_test_channel')).toBeInTheDocument();
expect(screen.getByText('button_return')).toBeInTheDocument();
});
});

View File

@@ -0,0 +1,31 @@
import { screen } from 'tests/test-utils';
export const testLabelInputAndHelpValue = ({
labelText,
testId,
helpText,
required = false,
value,
}: {
labelText: string;
testId: string;
helpText?: string;
required?: boolean;
value?: string;
}): void => {
const label = screen.getByText(labelText);
expect(label).toBeInTheDocument();
const input = screen.getByTestId(testId);
expect(input).toBeInTheDocument();
if (helpText !== undefined) {
expect(screen.getByText(helpText)).toBeInTheDocument();
}
if (required) {
expect(input).toBeRequired();
}
if (value) {
expect(input).toHaveValue(value);
}
};

View File

@@ -1,13 +1,15 @@
import { PlusOutlined } from '@ant-design/icons';
import { Tooltip, Typography } from 'antd';
import getAll from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import Spinner from 'components/Spinner';
import TextToolTip from 'components/TextToolTip';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import history from 'lib/history';
import { useCallback } from 'react';
import { isUndefined } from 'lodash-es';
import { useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
@@ -31,6 +33,14 @@ function AlertChannels(): JSX.Element {
const { loading, payload, error, errorMessage } = useFetch(getAll);
useEffect(() => {
if (!isUndefined(payload)) {
logEvent('Alert Channel: Channel list page visited', {
number: payload?.length,
});
}
}, [payload]);
if (error) {
return <Typography>{errorMessage}</Typography>;
}

View File

@@ -12,6 +12,7 @@ import { ColumnType, TablePaginationConfig } from 'antd/es/table';
import { FilterValue, SorterResult } from 'antd/es/table/interface';
import { ColumnsType } from 'antd/lib/table';
import { FilterConfirmProps } from 'antd/lib/table/interface';
import logEvent from 'api/common/logEvent';
import getAll from 'api/errors/getAll';
import getErrorCounts from 'api/errors/getErrorCounts';
import { ResizeTable } from 'components/ResizeTable';
@@ -23,7 +24,8 @@ import { convertRawQueriesToTraceSelectedTags } from 'hooks/useResourceAttribute
import useUrlQuery from 'hooks/useUrlQuery';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { useCallback, useEffect, useMemo } from 'react';
import { isUndefined } from 'lodash-es';
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
import { useSelector } from 'react-redux';
@@ -410,6 +412,26 @@ function AllErrors(): JSX.Element {
[pathname],
);
const logEventCalledRef = useRef(false);
useEffect(() => {
if (
!logEventCalledRef.current &&
!isUndefined(errorCountResponse.data?.payload)
) {
const selectedEnvironments = queries.find(
(val) => val.tagKey === 'resource_deployment_environment',
)?.tagValue;
logEvent('Exception: List page visited', {
numberOfExceptions: errorCountResponse?.data?.payload,
selectedEnvironments,
resourceAttributeUsed: !!queries?.length,
});
logEventCalledRef.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [errorCountResponse.data?.payload]);
return (
<ResizeTable
columns={columns}

View File

@@ -5,7 +5,6 @@
.app-content {
width: calc(100% - 64px);
overflow: auto;
z-index: 0;
.content-container {

View File

@@ -6,10 +6,10 @@ import './AppLayout.styles.scss';
import * as Sentry from '@sentry/react';
import { Flex } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion';
import cx from 'classnames';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { IS_SIDEBAR_COLLAPSED } from 'constants/app';
import ROUTES from 'constants/routes';
import SideNav from 'container/SideNav';
@@ -38,7 +38,6 @@ import { sideBarCollapse } from 'store/actions';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import {
UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
UPDATE_LATEST_VERSION,
@@ -66,11 +65,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const { pathname } = useLocation();
const { t } = useTranslation(['titles']);
const [
getUserVersionResponse,
getUserLatestVersionResponse,
getDynamicConfigsResponse,
] = useQueries([
const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([
{
queryFn: getUserVersion,
queryKey: ['getUserVersion', user?.accessJwt],
@@ -81,10 +76,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
queryKey: ['getUserLatestVersion', user?.accessJwt],
enabled: isLoggedIn,
},
{
queryFn: getDynamicConfigs,
queryKey: ['getDynamicConfigs', user?.accessJwt],
},
]);
useEffect(() => {
@@ -95,15 +86,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
if (getUserVersionResponse.status === 'idle' && isLoggedIn) {
getUserVersionResponse.refetch();
}
if (getDynamicConfigsResponse.status === 'idle') {
getDynamicConfigsResponse.refetch();
}
}, [
getUserLatestVersionResponse,
getUserVersionResponse,
isLoggedIn,
getDynamicConfigsResponse,
]);
}, [getUserLatestVersionResponse, getUserVersionResponse, isLoggedIn]);
const { children } = props;
@@ -111,7 +94,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const latestCurrentCounter = useRef(0);
const latestVersionCounter = useRef(0);
const latestConfigCounter = useRef(0);
const { notifications } = useNotifications();
@@ -189,23 +171,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
},
});
}
if (
getDynamicConfigsResponse.isFetched &&
getDynamicConfigsResponse.isSuccess &&
getDynamicConfigsResponse.data &&
getDynamicConfigsResponse.data.payload &&
latestConfigCounter.current === 0
) {
latestConfigCounter.current = 1;
dispatch({
type: UPDATE_CONFIGS,
payload: {
configs: getDynamicConfigsResponse.data.payload,
},
});
}
}, [
dispatch,
isLoggedIn,
@@ -220,9 +185,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
getUserLatestVersionResponse.isFetched,
getUserVersionResponse.isFetched,
getUserLatestVersionResponse.isSuccess,
getDynamicConfigsResponse.data,
getDynamicConfigsResponse.isFetched,
getDynamicConfigsResponse.isSuccess,
notifications,
]);
@@ -236,7 +198,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING ||
pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING ||
pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT ||
pathname === ROUTES.GET_STARTED_AWS_MONITORING;
pathname === ROUTES.GET_STARTED_AWS_MONITORING ||
pathname === ROUTES.GET_STARTED_AZURE_MONITORING;
const [showTrialExpiryBanner, setShowTrialExpiryBanner] = useState(false);
@@ -341,24 +304,29 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
collapsed={collapsed}
/>
)}
<div className={cx('app-content', collapsed ? 'collapsed' : '')}>
<div
className={cx('app-content', collapsed ? 'collapsed' : '')}
data-overlayscrollbars-initialize
>
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<LayoutContent>
<ChildrenContainer
style={{
margin:
isLogsView() ||
isTracesView() ||
isDashboardView() ||
isDashboardWidgetView() ||
isDashboardListView()
? 0
: '0 1rem',
}}
>
{isToDisplayLayout && !renderFullScreen && <TopNav />}
{children}
</ChildrenContainer>
<LayoutContent data-overlayscrollbars-initialize>
<OverlayScrollbar>
<ChildrenContainer
style={{
margin:
isLogsView() ||
isTracesView() ||
isDashboardView() ||
isDashboardWidgetView() ||
isDashboardListView()
? 0
: '0 1rem',
}}
>
{isToDisplayLayout && !renderFullScreen && <TopNav />}
{children}
</ChildrenContainer>
</OverlayScrollbar>
</LayoutContent>
</Sentry.ErrorBoundary>
</div>

View File

@@ -13,7 +13,6 @@ export const Layout = styled(LayoutComponent)`
`;
export const LayoutContent = styled(LayoutComponent.Content)`
overflow-y: auto;
height: 100%;
&::-webkit-scrollbar {
width: 0.1rem;

View File

@@ -19,10 +19,10 @@ import { ColumnsType } from 'antd/es/table';
import updateCreditCardApi from 'api/billing/checkout';
import getUsage, { UsageResponsePayloadProps } from 'api/billing/getUsage';
import manageCreditCardApi from 'api/billing/manage';
import logEvent from 'api/common/logEvent';
import Spinner from 'components/Spinner';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import useAnalytics from 'hooks/analytics/useAnalytics';
import useAxiosError from 'hooks/useAxiosError';
import useLicense from 'hooks/useLicense';
import { useNotifications } from 'hooks/useNotifications';
@@ -137,8 +137,6 @@ export default function BillingContainer(): JSX.Element {
Partial<UsageResponsePayloadProps>
>({});
const { trackEvent } = useAnalytics();
const { isFetching, data: licensesData, error: licenseError } = useLicense();
const { user, org } = useSelector<AppState, AppReducer>((state) => state.app);
@@ -316,7 +314,7 @@ export default function BillingContainer(): JSX.Element {
const handleBilling = useCallback(async () => {
if (isFreeTrial && !licensesData?.payload?.trialConvertedToSubscription) {
trackEvent('Billing : Upgrade Plan', {
logEvent('Billing : Upgrade Plan', {
user: pick(user, ['email', 'userId', 'name']),
org,
});
@@ -327,7 +325,7 @@ export default function BillingContainer(): JSX.Element {
cancelURL: window.location.href,
});
} else {
trackEvent('Billing : Manage Billing', {
logEvent('Billing : Manage Billing', {
user: pick(user, ['email', 'userId', 'name']),
org,
});

View File

@@ -11,11 +11,12 @@ import testOpsGenie from 'api/channels/testOpsgenie';
import testPagerApi from 'api/channels/testPager';
import testSlackApi from 'api/channels/testSlack';
import testWebhookApi from 'api/channels/testWebhook';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import FormAlertChannels from 'container/FormAlertChannels';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
@@ -43,6 +44,10 @@ function CreateAlertChannels({
const [formInstance] = Form.useForm();
useEffect(() => {
logEvent('Alert Channel: Create channel page visited', {});
}, []);
const [selectedConfig, setSelectedConfig] = useState<
Partial<
SlackChannel &
@@ -139,19 +144,25 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareSlackRequest, t, notifications]);
const prepareWebhookRequest = useCallback(() => {
@@ -200,19 +211,25 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareWebhookRequest, t, notifications]);
const preparePagerRequest = useCallback(() => {
@@ -245,8 +262,8 @@ function CreateAlertChannels({
setSavingState(true);
const request = preparePagerRequest();
if (request) {
try {
try {
if (request) {
const response = await createPagerApi(request);
if (response.statusCode === 200) {
@@ -255,20 +272,31 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return { status: 'success', statusMessage: t('channel_creation_done') };
}
} catch (e) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
}
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [t, notifications, preparePagerRequest]);
const prepareOpsgenieRequest = useCallback(
@@ -295,19 +323,25 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareOpsgenieRequest, t, notifications]);
const prepareEmailRequest = useCallback(
@@ -332,19 +366,25 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareEmailRequest, t, notifications]);
const prepareMsTeamsRequest = useCallback(
@@ -370,19 +410,25 @@ function CreateAlertChannels({
description: t('channel_creation_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return { status: 'success', statusMessage: t('channel_creation_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_creation_failed'),
});
return {
status: 'failed',
statusMessage: response.error || t('channel_creation_failed'),
};
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_creation_failed'),
});
return { status: 'failed', statusMessage: t('channel_creation_failed') };
} finally {
setSavingState(false);
}
setSavingState(false);
}, [prepareMsTeamsRequest, t, notifications]);
const onSaveHandler = useCallback(
@@ -400,7 +446,15 @@ function CreateAlertChannels({
const functionToCall = functionMapper[value as keyof typeof functionMapper];
if (functionToCall) {
functionToCall();
const result = await functionToCall();
logEvent('Alert Channel: Save channel', {
type: value,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'true',
status: result?.status,
statusMessage: result?.statusMessage,
});
} else {
notifications.error({
message: 'Error',
@@ -409,6 +463,7 @@ function CreateAlertChannels({
}
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
onSlackHandler,
onWebhookHandler,
@@ -472,14 +527,25 @@ function CreateAlertChannels({
description: t('channel_test_failed'),
});
}
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'true',
status:
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
});
} catch (error) {
notifications.error({
message: 'Error',
description: t('channel_test_unexpected'),
});
}
setTestingState(false);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
prepareWebhookRequest,
t,

View File

@@ -1,4 +1,6 @@
import { Row, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { AlertTypes } from 'types/api/alerts/alertTypes';
@@ -34,6 +36,13 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
default:
break;
}
logEvent('Alert: Sample alert link clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[option],
link: url,
page: 'New alert data source selection page',
});
window.open(url, '_blank');
}
const renderOptions = useMemo(

View File

@@ -1,4 +1,5 @@
import { Form, Row } from 'antd';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { QueryParams } from 'constants/query';
import FormAlertRules from 'container/FormAlertRules';
@@ -68,6 +69,8 @@ function CreateRules(): JSX.Element {
useEffect(() => {
if (alertType) {
onSelectType(alertType);
} else {
logEvent('Alert: New alert data source selection page visited', {});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [alertType]);

View File

@@ -11,6 +11,7 @@ import testOpsgenie from 'api/channels/testOpsgenie';
import testPagerApi from 'api/channels/testPager';
import testSlackApi from 'api/channels/testSlack';
import testWebhookApi from 'api/channels/testWebhook';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import {
ChannelType,
@@ -89,7 +90,7 @@ function EditAlertChannels({
description: t('webhook_url_required'),
});
setSavingState(false);
return;
return { status: 'failed', statusMessage: t('webhook_url_required') };
}
const response = await editSlackApi(prepareSlackRequest());
@@ -101,13 +102,17 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareSlackRequest, t, notifications, selectedConfig]);
const prepareWebhookRequest = useCallback(() => {
@@ -136,13 +141,13 @@ function EditAlertChannels({
if (selectedConfig?.api_url === '') {
showError(t('webhook_url_required'));
setSavingState(false);
return;
return { status: 'failed', statusMessage: t('webhook_url_required') };
}
if (username && (!password || password === '')) {
showError(t('username_no_password'));
setSavingState(false);
return;
return { status: 'failed', statusMessage: t('username_no_password') };
}
const response = await editWebhookApi(prepareWebhookRequest());
@@ -154,10 +159,15 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
showError(response.error || t('channel_edit_failed'));
return { status: 'success', statusMessage: t('channel_edit_done') };
}
showError(response.error || t('channel_edit_failed'));
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareWebhookRequest, t, notifications, selectedConfig]);
const prepareEmailRequest = useCallback(
@@ -181,13 +191,18 @@ function EditAlertChannels({
description: t('channel_edit_done'),
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareEmailRequest, t, notifications]);
const preparePagerRequest = useCallback(
@@ -218,7 +233,7 @@ function EditAlertChannels({
description: validationError,
});
setSavingState(false);
return;
return { status: 'failed', statusMessage: validationError };
}
const response = await editPagerApi(preparePagerRequest());
@@ -229,13 +244,18 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [preparePagerRequest, notifications, selectedConfig, t]);
const prepareOpsgenieRequest = useCallback(
@@ -259,7 +279,7 @@ function EditAlertChannels({
description: t('api_key_required'),
});
setSavingState(false);
return;
return { status: 'failed', statusMessage: t('api_key_required') };
}
const response = await editOpsgenie(prepareOpsgenieRequest());
@@ -271,13 +291,18 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareOpsgenieRequest, t, notifications, selectedConfig]);
const prepareMsTeamsRequest = useCallback(
@@ -301,7 +326,7 @@ function EditAlertChannels({
description: t('webhook_url_required'),
});
setSavingState(false);
return;
return { status: 'failed', statusMessage: t('webhook_url_required') };
}
const response = await editMsTeamsApi(prepareMsTeamsRequest());
@@ -313,31 +338,46 @@ function EditAlertChannels({
});
history.replace(ROUTES.ALL_CHANNELS);
} else {
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
return { status: 'success', statusMessage: t('channel_edit_done') };
}
notifications.error({
message: 'Error',
description: response.error || t('channel_edit_failed'),
});
setSavingState(false);
return {
status: 'failed',
statusMessage: response.error || t('channel_edit_failed'),
};
}, [prepareMsTeamsRequest, t, notifications, selectedConfig]);
const onSaveHandler = useCallback(
(value: ChannelType) => {
async (value: ChannelType) => {
let result;
if (value === ChannelType.Slack) {
onSlackEditHandler();
result = await onSlackEditHandler();
} else if (value === ChannelType.Webhook) {
onWebhookEditHandler();
result = await onWebhookEditHandler();
} else if (value === ChannelType.Pagerduty) {
onPagerEditHandler();
result = await onPagerEditHandler();
} else if (value === ChannelType.MsTeams) {
onMsTeamsEditHandler();
result = await onMsTeamsEditHandler();
} else if (value === ChannelType.Opsgenie) {
onOpsgenieEditHandler();
result = await onOpsgenieEditHandler();
} else if (value === ChannelType.Email) {
onEmailEditHandler();
result = await onEmailEditHandler();
}
logEvent('Alert Channel: Save channel', {
type: value,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'false',
status: result?.status,
statusMessage: result?.statusMessage,
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
onSlackEditHandler,
onWebhookEditHandler,
@@ -399,6 +439,14 @@ function EditAlertChannels({
description: t('channel_test_failed'),
});
}
logEvent('Alert Channel: Test notification', {
type: channelType,
sendResolvedAlert: selectedConfig?.send_resolved,
name: selectedConfig?.name,
new: 'false',
status:
response && response.statusCode === 200 ? 'Test success' : 'Test failed',
});
} catch (error) {
notifications.error({
message: 'Error',
@@ -407,6 +455,7 @@ function EditAlertChannels({
}
setTestingState(false);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
t,
prepareWebhookRequest,

View File

@@ -1,8 +1,34 @@
import './EmptyLogsSearch.styles.scss';
import { Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { useEffect, useRef } from 'react';
import { DataSource, PanelTypeKeys } from 'types/common/queryBuilder';
export default function EmptyLogsSearch({
dataSource,
panelType,
}: {
dataSource: DataSource;
panelType: PanelTypeKeys;
}): JSX.Element {
const logEventCalledRef = useRef(false);
useEffect(() => {
if (!logEventCalledRef.current) {
if (dataSource === DataSource.TRACES) {
logEvent('Traces Explorer: No results', {
panelType,
});
} else if (dataSource === DataSource.LOGS) {
logEvent('Logs Explorer: No results', {
panelType,
});
}
logEventCalledRef.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
export default function EmptyLogsSearch(): JSX.Element {
return (
<div className="empty-logs-search-container">
<div className="empty-logs-search-container-content">

View File

@@ -1,6 +1,7 @@
import './styles.scss';
import { Button, Divider, Space, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import getNextPrevId from 'api/errors/getNextPrevId';
import Editor from 'components/Editor';
import { ResizeTable } from 'components/ResizeTable';
@@ -9,8 +10,9 @@ import dayjs from 'dayjs';
import { useNotifications } from 'hooks/useNotifications';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { isUndefined } from 'lodash-es';
import { urlKey } from 'pages/ErrorDetails/utils';
import { useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
@@ -111,9 +113,29 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
}));
const onClickTraceHandler = (): void => {
logEvent('Exception: Navigate to trace detail page', {
groupId: errorDetail?.groupID,
spanId: errorDetail.spanID,
traceId: errorDetail.traceID,
exceptionId: errorDetail?.errorId,
});
history.push(`/trace/${errorDetail.traceID}?spanId=${errorDetail.spanID}`);
};
const logEventCalledRef = useRef(false);
useEffect(() => {
if (!logEventCalledRef.current && !isUndefined(data)) {
logEvent('Exception: Detail page visited', {
groupId: errorDetail?.groupID,
spanId: errorDetail.spanID,
traceId: errorDetail.traceID,
exceptionId: errorDetail?.errorId,
});
logEventCalledRef.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
return (
<>
<Typography>{errorDetail.exceptionType}</Typography>

View File

@@ -91,8 +91,7 @@
box-shadow: none !important;
&.ant-btn-round {
padding-inline-start: 10px;
padding-inline-end: 8px;
padding: 8px 12px 8px 10px;
font-weight: 500;
}

View File

@@ -14,6 +14,7 @@ import {
Tooltip,
Typography,
} from 'antd';
import logEvent from 'api/common/logEvent';
import axios from 'axios';
import cx from 'classnames';
import { getViewDetailsUsingViewKey } from 'components/ExplorerCard/utils';
@@ -93,7 +94,23 @@ function ExplorerOptions({
setIsExport(value);
}, []);
const {
currentQuery,
panelType,
isStagedQueryUpdated,
redirectWithQueryBuilderData,
} = useQueryBuilder();
const handleSaveViewModalToggle = (): void => {
if (sourcepage === DataSource.TRACES) {
logEvent('Traces Explorer: Save view clicked', {
panelType,
});
} else if (sourcepage === DataSource.LOGS) {
logEvent('Logs Explorer: Save view clicked', {
panelType,
});
}
setIsSaveModalOpen(!isSaveModalOpen);
};
@@ -104,11 +121,21 @@ function ExplorerOptions({
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const onCreateAlertsHandler = useCallback(() => {
if (sourcepage === DataSource.TRACES) {
logEvent('Traces Explorer: Create alert', {
panelType,
});
} else if (sourcepage === DataSource.LOGS) {
logEvent('Logs Explorer: Create alert', {
panelType,
});
}
history.push(
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
JSON.stringify(query),
)}`,
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [history, query]);
const onCancel = (value: boolean) => (): void => {
@@ -116,6 +143,15 @@ function ExplorerOptions({
};
const onAddToDashboard = (): void => {
if (sourcepage === DataSource.TRACES) {
logEvent('Traces Explorer: Add to dashboard clicked', {
panelType,
});
} else if (sourcepage === DataSource.LOGS) {
logEvent('Logs Explorer: Add to dashboard clicked', {
panelType,
});
}
setIsExport(true);
};
@@ -127,13 +163,6 @@ function ExplorerOptions({
refetch: refetchAllView,
} = useGetAllViews(sourcepage);
const {
currentQuery,
panelType,
isStagedQueryUpdated,
redirectWithQueryBuilderData,
} = useQueryBuilder();
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);
const viewName = useGetSearchQueryParam(QueryParams.viewName) || '';
@@ -224,6 +253,17 @@ function ExplorerOptions({
onMenuItemSelectHandler({
key: option.key,
});
if (sourcepage === DataSource.TRACES) {
logEvent('Traces Explorer: Select view', {
panelType,
viewName: option?.value,
});
} else if (sourcepage === DataSource.LOGS) {
logEvent('Logs Explorer: Select view', {
panelType,
viewName: option?.value,
});
}
if (ref.current) {
ref.current.blur();
}
@@ -259,6 +299,17 @@ function ExplorerOptions({
viewName: newViewName,
setNewViewName,
});
if (sourcepage === DataSource.TRACES) {
logEvent('Traces Explorer: Save view successful', {
panelType,
viewName: newViewName,
});
} else if (sourcepage === DataSource.LOGS) {
logEvent('Logs Explorer: Save view successful', {
panelType,
viewName: newViewName,
});
}
};
// TODO: Remove this and move this to scss file
@@ -499,7 +550,7 @@ function ExplorerOptions({
export interface ExplorerOptionsProps {
isLoading?: boolean;
onExport: (dashboard: Dashboard | null) => void;
onExport: (dashboard: Dashboard | null, isNewDashboard?: boolean) => void;
query: Query | null;
disabled: boolean;
sourcepage: DataSource;

View File

@@ -1,5 +1,6 @@
import { Button, Typography } from 'antd';
import createDashboard from 'api/dashboard/create';
import { ENTITY_VERSION_V4 } from 'constants/app';
import { useGetAllDashboard } from 'hooks/dashboard/useGetAllDashboard';
import useAxiosError from 'hooks/useAxiosError';
import { useCallback, useMemo, useState } from 'react';
@@ -40,7 +41,7 @@ function ExportPanelContainer({
} = useMutation(createDashboard, {
onSuccess: (data) => {
if (data.payload) {
onExport(data?.payload);
onExport(data?.payload, true);
}
refetch();
},
@@ -54,7 +55,7 @@ function ExportPanelContainer({
({ uuid }) => uuid === selectedDashboardId,
);
onExport(currentSelectedDashboard || null);
onExport(currentSelectedDashboard || null, false);
}, [data, selectedDashboardId, onExport]);
const handleSelect = useCallback(
@@ -70,6 +71,7 @@ function ExportPanelContainer({
ns: 'dashboard',
}),
uploadedGrafana: false,
version: ENTITY_VERSION_V4,
});
}, [t, createNewDashboard]);

View File

@@ -40,7 +40,7 @@ function ExportPanel({
export interface ExportPanelProps {
isLoading?: boolean;
onExport: (dashboard: Dashboard | null) => void;
onExport: (dashboard: Dashboard | null, isNewDashboard?: boolean) => void;
query: Query | null;
}

View File

@@ -27,6 +27,7 @@ function EmailForm({ setSelectedConfig }: EmailFormProps): JSX.Element {
<Input
onChange={handleInputChange('to')}
placeholder={t('placeholder_email_to')}
data-testid="email-to-textbox"
/>
</Form.Item>

View File

@@ -17,6 +17,7 @@ function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
webhook_url: event.target.value,
}));
}}
data-testid="webhook-url-textbox"
/>
</Form.Item>
@@ -30,6 +31,7 @@ function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
title: event.target.value,
}))
}
data-testid="title-textarea"
/>
</Form.Item>
@@ -41,6 +43,7 @@ function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
text: event.target.value,
}))
}
data-testid="description-textarea"
placeholder={t('placeholder_slack_description')}
/>
</Form.Item>

View File

@@ -20,7 +20,10 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
return (
<>
<Form.Item name="api_key" label={t('field_opsgenie_api_key')} required>
<Input onChange={handleInputChange('api_key')} />
<Input
onChange={handleInputChange('api_key')}
data-testid="opsgenie-api-key-textbox"
/>
</Form.Item>
<Form.Item
@@ -33,6 +36,7 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
rows={4}
onChange={handleInputChange('message')}
placeholder={t('placeholder_opsgenie_message')}
data-testid="opsgenie-message-textarea"
/>
</Form.Item>
@@ -46,6 +50,7 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
rows={4}
onChange={handleInputChange('description')}
placeholder={t('placeholder_opsgenie_description')}
data-testid="opsgenie-description-textarea"
/>
</Form.Item>
@@ -59,6 +64,7 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
rows={4}
onChange={handleInputChange('priority')}
placeholder={t('placeholder_opsgenie_priority')}
data-testid="opsgenie-priority-textarea"
/>
</Form.Item>
</>

View File

@@ -18,6 +18,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
routing_key: event.target.value,
}));
}}
data-testid="pager-routing-key-textbox"
/>
</Form.Item>
@@ -36,6 +37,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
}))
}
placeholder={t('placeholder_pager_description')}
data-testid="pager-description-textarea"
/>
</Form.Item>
@@ -51,6 +53,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
severity: event.target.value,
}))
}
data-testid="pager-severity-textbox"
/>
</Form.Item>
@@ -67,6 +70,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
details: event.target.value,
}))
}
data-testid="pager-additional-details-textarea"
/>
</Form.Item>
@@ -97,6 +101,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
group: event.target.value,
}))
}
data-testid="pager-group-textarea"
/>
</Form.Item>
@@ -112,6 +117,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
class: event.target.value,
}))
}
data-testid="pager-class-textarea"
/>
</Form.Item>
<Form.Item
@@ -126,6 +132,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
client: event.target.value,
}))
}
data-testid="pager-client-textarea"
/>
</Form.Item>
@@ -141,6 +148,7 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
client_url: event.target.value,
}))
}
data-testid="pager-client-url-textarea"
/>
</Form.Item>
</>

View File

@@ -19,6 +19,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
api_url: event.target.value,
}));
}}
data-testid="webhook-url-textbox"
/>
</Form.Item>
@@ -34,11 +35,13 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
channel: event.target.value,
}))
}
data-testid="slack-channel-textbox"
/>
</Form.Item>
<Form.Item name="title" label={t('field_slack_title')}>
<TextArea
data-testid="title-textarea"
rows={4}
// value={`[{{ .Status | toUpper }}{{ if eq .Status \"firing\" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}\n{{- if gt (len .CommonLabels) (len .GroupLabels) -}}\n{{\" \"}}(\n{{- with .CommonLabels.Remove .GroupLabels.Names }}\n {{- range $index, $label := .SortedPairs -}}\n {{ if $index }}, {{ end }}\n {{- $label.Name }}=\"{{ $label.Value -}}\"\n {{- end }}\n{{- end -}}\n)\n{{- end }}`}
onChange={(event): void =>
@@ -59,6 +62,7 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
}))
}
placeholder={t('placeholder_slack_description')}
data-testid="description-textarea"
/>
</Form.Item>
</>

View File

@@ -17,6 +17,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
api_url: event.target.value,
}));
}}
data-testid="webhook-url-textbox"
/>
</Form.Item>
<Form.Item
@@ -31,6 +32,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
username: event.target.value,
}));
}}
data-testid="webhook-username-textbox"
/>
</Form.Item>
<Form.Item
@@ -46,6 +48,7 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
password: event.target.value,
}));
}}
data-testid="webhook-password-textbox"
/>
</Form.Item>
</>

View File

@@ -85,6 +85,7 @@ function FormAlertChannels({
<Form initialValues={initialValue} layout="vertical" form={formInstance}>
<Form.Item label={t('field_channel_name')} labelAlign="left" name="name">
<Input
data-testid="channel-name-textbox"
disabled={editing}
onChange={(event): void => {
setSelectedConfig((state) => ({
@@ -102,6 +103,7 @@ function FormAlertChannels({
>
<Switch
defaultChecked={initialValue?.send_resolved}
data-testid="field-send-resolved-checkbox"
onChange={(value): void => {
setSelectedConfig((state) => ({
...state,
@@ -112,24 +114,37 @@ function FormAlertChannels({
</Form.Item>
<Form.Item label={t('field_channel_type')} labelAlign="left" name="type">
<Select disabled={editing} onChange={onTypeChangeHandler} value={type}>
<Select.Option value="slack" key="slack">
<Select
disabled={editing}
onChange={onTypeChangeHandler}
value={type}
data-testid="channel-type-select"
>
<Select.Option value="slack" key="slack" data-testid="select-option">
Slack
</Select.Option>
<Select.Option value="webhook" key="webhook">
<Select.Option value="webhook" key="webhook" data-testid="select-option">
Webhook
</Select.Option>
<Select.Option value="pagerduty" key="pagerduty">
<Select.Option
value="pagerduty"
key="pagerduty"
data-testid="select-option"
>
Pagerduty
</Select.Option>
<Select.Option value="opsgenie" key="opsgenie">
<Select.Option
value="opsgenie"
key="opsgenie"
data-testid="select-option"
>
Opsgenie
</Select.Option>
<Select.Option value="email" key="email">
<Select.Option value="email" key="email" data-testid="select-option">
Email
</Select.Option>
{!isOssFeature?.active && (
<Select.Option value="msteams" key="msteams">
<Select.Option value="msteams" key="msteams" data-testid="select-option">
<div>
Microsoft Teams {!isUserOnEEPlan && '(Supported in Paid Plans Only)'}{' '}
</div>

View File

@@ -3,6 +3,8 @@ import './FormAlertRules.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Form, Select, Switch, Tooltip } from 'antd';
import getChannels from 'api/channels/getAll';
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
@@ -10,6 +12,7 @@ import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { AlertDef, Labels } from 'types/api/alerts/def';
import AppReducer from 'types/reducer/app';
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
@@ -73,9 +76,24 @@ function BasicInfo({
const noChannels = channels.payload?.length === 0;
const handleCreateNewChannels = useCallback(() => {
logEvent('Alert: Create notification channel button clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
ruleId: isNewRule ? 0 : alertDef?.id,
});
window.open(ROUTES.CHANNELS_NEW, '_blank');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (!channels.loading && isNewRule) {
logEvent('Alert: New alert creation page visited', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
numberOfChannels: channels?.payload?.length,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [channels.payload, channels.loading]);
return (
<>
<StepHeading> {t('alert_form_step3')} </StepHeading>

View File

@@ -48,6 +48,7 @@ export interface ChartPreviewProps {
userQueryKey?: string;
allowSelectedIntervalForStepGen?: boolean;
yAxisUnit: string;
setQueryStatus?: (status: string) => void;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
@@ -62,6 +63,7 @@ function ChartPreview({
allowSelectedIntervalForStepGen = false,
alertDef,
yAxisUnit,
setQueryStatus,
}: ChartPreviewProps): JSX.Element | null {
const { t } = useTranslation('alerts');
const dispatch = useDispatch();
@@ -149,10 +151,10 @@ function ChartPreview({
useEffect((): void => {
const { startTime, endTime } = getTimeRange(queryResponse);
if (setQueryStatus) setQueryStatus(queryResponse.status);
setMinTimeScale(startTime);
setMaxTimeScale(endTime);
}, [maxTime, minTime, globalSelectedInterval, queryResponse]);
}, [maxTime, minTime, globalSelectedInterval, queryResponse, setQueryStatus]);
if (queryResponse.data && graphType === PANEL_TYPES.BAR) {
const sortedSeriesData = getSortedSeriesData(
@@ -246,17 +248,19 @@ function ChartPreview({
return (
<ChartContainer>
{headline}
{(queryResponse?.isError || queryResponse?.error) && (
<FailedMessageContainer color="red" title="Failed to refresh the chart">
<InfoCircleOutlined />{' '}
{queryResponse.error.message || t('preview_chart_unexpected_error')}
</FailedMessageContainer>
)}
{chartData && !queryResponse.isError && (
<div ref={graphRef} style={{ height: '100%' }}>
{queryResponse.isLoading && (
<Spinner size="large" tip="Loading..." height="100%" />
)}
<div ref={graphRef} style={{ height: '100%' }}>
{queryResponse.isLoading && (
<Spinner size="large" tip="Loading..." height="100%" />
)}
{(queryResponse?.isError || queryResponse?.error) && (
<FailedMessageContainer color="red" title="Failed to refresh the chart">
<InfoCircleOutlined />{' '}
{queryResponse.error.message || t('preview_chart_unexpected_error')}
</FailedMessageContainer>
)}
{chartData && !queryResponse.isError && (
<GridPanelSwitch
options={options}
panelType={graphType}
@@ -268,8 +272,8 @@ function ChartPreview({
query={query || initialQueriesMap.metrics}
yAxisUnit={yAxisUnit}
/>
</div>
)}
)}
</div>
</ChartContainer>
);
}
@@ -282,6 +286,7 @@ ChartPreview.defaultProps = {
userQueryKey: '',
allowSelectedIntervalForStepGen: false,
alertDef: undefined,
setQueryStatus: (): void => {},
};
export default ChartPreview;

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