Compare commits

..

436 Commits

Author SHA1 Message Date
Naman Verma
852ce0cfce Merge branch 'nv/v2-list-dashboard' into nv/dashboard-views 2026-06-08 10:50:06 +05:30
Naman Verma
62c0c52bec fix: use correct perses package in list v2 file 2026-06-08 10:46:04 +05:30
Naman Verma
36dd325e9a Merge branch 'main' into nv/v2-list-dashboard 2026-06-08 10:42:23 +05:30
Naman Verma
c51ef198fe fix: use ESCAPE literal in contains and like operators 2026-06-05 16:35:32 +05:30
Naman Verma
1b4f1c7161 chore: use go sqlbuilder 2026-06-05 15:23:27 +05:30
Naman Verma
f82dd1b96a fix: remove public filter from visitor 2026-06-05 11:58:17 +05:30
Naman Verma
f0deed6e0f fix: dont allow system dashboards to be deleted 2026-06-05 11:51:19 +05:30
Naman Verma
e8791fe1ed fix: make GettableTag a defined type instead of an alias 2026-06-05 11:46:28 +05:30
Naman Verma
4fe9222a5c Merge branch 'nv/v2-list-dashboard' into nv/dashboard-views 2026-06-05 02:43:45 +05:30
Naman Verma
7d9d4b923d chore: regenerate api specs 2026-06-05 02:43:25 +05:30
Naman Verma
67ed248fef chore: regenerate api specs 2026-06-05 02:41:57 +05:30
Naman Verma
77cb60c106 Merge branch 'nv/v2-list-dashboard' into nv/dashboard-views 2026-06-05 01:01:22 +05:30
Naman Verma
1fbe05f450 Merge branch 'main' into nv/v2-list-dashboard 2026-06-05 01:00:34 +05:30
Naman Verma
43d588a870 fix: add missing image field in list response 2026-06-05 00:58:27 +05:30
Naman Verma
6309e27168 chore: add nolint comment 2026-06-04 18:27:21 +05:30
Naman Verma
564e79137e fix: use must method for user id as well 2026-06-04 18:03:31 +05:30
Naman Verma
92ef8b73a9 fix: remove wrong api description msg 2026-06-04 17:56:06 +05:30
Naman Verma
98df6ead80 Merge branch 'main' into nv/v2-list-dashboard 2026-06-04 17:50:01 +05:30
Naman Verma
7c7329cfd7 Merge branch 'nv/v2-list-dashboard' into nv/dashboard-views 2026-06-04 17:28:10 +05:30
Naman Verma
00a99999ac feat: include list of all dashboard tags in list api response 2026-06-04 17:27:46 +05:30
Naman Verma
fd1a94ca41 fix: use must new org id 2026-06-04 17:18:26 +05:30
Naman Verma
29e8f651f5 Merge branch 'nv/v2-list-dashboard' into nv/dashboard-views 2026-06-04 17:16:56 +05:30
Naman Verma
3809dc1d31 fix: use must new org id 2026-06-04 17:16:15 +05:30
Naman Verma
9a5a24392f Merge branch 'nv/v2-list-dashboard' into nv/dashboard-views 2026-06-04 17:13:18 +05:30
Naman Verma
de42bc9f73 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-06-04 17:12:56 +05:30
Naman Verma
0ddadb051d chore: rename updateable to updatable 2026-06-04 17:10:12 +05:30
Naman Verma
9e8da30c3b fix: proper error passage 2026-06-04 17:04:54 +05:30
Naman Verma
97de2551eb fix: use must new org id 2026-06-04 17:04:37 +05:30
Naman Verma
67affa0fd5 chore: rearrange 2026-06-04 16:58:26 +05:30
Naman Verma
226d924489 chore: add all err codes in api spec 2026-06-04 16:45:32 +05:30
Naman Verma
643770d790 fix: return better err 2026-06-04 16:44:35 +05:30
Naman Verma
f92473230a chore: rearrange 2026-06-04 16:34:51 +05:30
Naman Verma
93452ebde9 chore: regenerate api specs 2026-06-04 16:31:11 +05:30
Naman Verma
d9b316fc0a chore: dont pass email to create and update methods 2026-06-04 16:29:35 +05:30
Naman Verma
c5c86c1bbf chore: dont pass email to create and update methods 2026-06-04 16:27:18 +05:30
Naman Verma
19f68b1ac1 fix: remove user auditable 2026-06-04 16:25:28 +05:30
Naman Verma
932080a83c chore: bump migration number 2026-06-04 16:23:12 +05:30
Naman Verma
9135664188 fix: remove omitempty 2026-06-04 16:22:26 +05:30
Naman Verma
2d061f3a70 fix: fix post merge build and schema errors 2026-06-04 16:21:10 +05:30
Naman Verma
a08bd8d126 Merge branch 'nv/v2-list-dashboard' into nv/dashboard-views 2026-06-04 16:17:49 +05:30
Naman Verma
6a3d1b54eb Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-06-04 16:13:42 +05:30
Naman Verma
4c10fb0df1 Merge branch 'main' into nv/v2-dashboard-update 2026-06-04 16:13:18 +05:30
Naman Verma
deea23d441 fix: remove extra decodePatch calls 2026-06-03 20:50:16 +05:30
Naman Verma
3b2586f386 chore: better error passage 2026-06-03 20:04:31 +05:30
Naman Verma
cd44095dc7 chore: separate file for patch types 2026-06-03 19:50:42 +05:30
Naman Verma
fb2834f812 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-06-03 17:04:06 +05:30
Naman Verma
66534bc7c6 Merge branch 'main' into nv/v2-dashboard-update 2026-06-03 17:01:10 +05:30
Naman Verma
e8214739b8 fix: make remove idempotent in patch 2026-06-03 16:59:37 +05:30
Naman Verma
0a2fe1c8f6 chore: remove JSONPatchDocument and use patchable everywhere 2026-06-03 16:49:44 +05:30
Naman Verma
d40d2ddf60 chore: use same jsonpatch package as done in zeus 2026-06-03 16:10:10 +05:30
Naman Verma
879258ec28 test: integration test and fixes found through it 2026-06-03 16:05:52 +05:30
Naman Verma
a36c7fbde1 fix: use valuer string for list order and sort 2026-06-03 14:38:49 +05:30
Naman Verma
005718cc3b fix: remove join to public dashboard table in list call 2026-06-03 14:21:06 +05:30
Naman Verma
bc0623d07a test: change data to spec in unit tests 2026-06-03 14:09:24 +05:30
Naman Verma
7019df2470 fix: add all error codes for new apis 2026-06-03 13:53:35 +05:30
Naman Verma
faf877de65 fix: change title to name in api description 2026-06-03 13:43:35 +05:30
Naman Verma
5e5656881d fix: add missing name fetch in listv2 store method 2026-06-03 13:36:31 +05:30
Naman Verma
5d60c34761 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-06-03 13:30:12 +05:30
Naman Verma
9bf42aa1da Merge branch 'main' into nv/v2-dashboard-update 2026-06-03 13:28:38 +05:30
Naman Verma
75b422d236 chore: generate frontend api spec 2026-06-03 12:20:26 +05:30
Naman Verma
a6e3a6efa2 fix: address review comments 2026-06-03 12:19:22 +05:30
Naman Verma
158a2eac8b feat: delete dashboard v2 API (#11299)
* feat: delete dashboard v2 API

* fix: fix post merge build and spec errors
2026-06-03 11:29:16 +05:30
Naman Verma
c0e67a8ca3 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-06-03 11:23:52 +05:30
Naman Verma
9b7c820f87 Merge branch 'main' into nv/v2-dashboard-update 2026-06-03 11:21:38 +05:30
Srikanth Chekuri
43ff0dc90f Merge branch 'main' into nv/v2-dashboard-update 2026-06-02 15:44:11 +05:30
Naman Verma
c8cbac7725 chore: bump migration number 2026-05-29 08:10:18 +05:30
Naman Verma
5efa8d226b chore: update api spec 2026-05-29 08:09:10 +05:30
Naman Verma
cc1feb43d0 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-05-29 08:07:19 +05:30
Naman Verma
13d9641f9d chore: add back accidentally removed tests 2026-05-29 07:44:16 +05:30
Naman Verma
9f559d33d7 fix: change data to spec in api param description 2026-05-29 07:43:19 +05:30
Naman Verma
f56f7381a5 fix: use v1 store update method 2026-05-29 07:38:12 +05:30
Naman Verma
3656d117cc Merge branch 'main' into nv/v2-dashboard-update 2026-05-29 07:13:29 +05:30
Naman Verma
8e39f44a9e Merge branch 'main' into nv/v2-dashboard-update 2026-05-27 18:42:47 +05:30
Naman Verma
e92a0e953c fix: remove unused module methods 2026-05-27 17:56:30 +05:30
Naman Verma
7add0b924c fix: remove unused store method 2026-05-27 17:54:38 +05:30
Naman Verma
d2070d4844 Merge branch 'main' into nv/v2-dashboard-update 2026-05-27 17:25:31 +05:30
Naman Verma
e9b0c32f19 fix: fix build error in test after merge conflict 2026-05-27 17:20:53 +05:30
Naman Verma
ae18dbc722 Merge branch 'main' into nv/v2-dashboard-update 2026-05-27 17:19:18 +05:30
Naman Verma
6771e06d15 feat: introduce dashboard views for list dashboards page 2026-05-26 20:22:15 +05:30
Naman Verma
61b8a41c3d Merge branch 'nv/v2-list-dashboard' into nv/v2-dashboard-delete 2026-05-26 12:57:11 +05:30
Naman Verma
9789a5be89 chore: bump migration number 2026-05-26 12:56:59 +05:30
Naman Verma
09bf5267d6 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-05-26 12:56:42 +05:30
Naman Verma
87ff61e887 fix: build error fix 2026-05-26 12:56:10 +05:30
Naman Verma
3c14d0e53c Merge branch 'nv/v2-list-dashboard' into nv/v2-dashboard-delete 2026-05-26 12:55:27 +05:30
Naman Verma
f57fc261b9 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-05-26 12:54:24 +05:30
Naman Verma
8afb247046 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-update 2026-05-26 12:54:07 +05:30
Naman Verma
3ee706c68e chore: make tags required in postable 2026-05-26 12:52:10 +05:30
Naman Verma
58767e67c0 chore: generate api specs 2026-05-26 12:50:39 +05:30
Naman Verma
ccecc13bb9 feat: add flag to generate unique name in backend 2026-05-26 12:41:04 +05:30
Naman Verma
1c8b2c1d21 Merge branch 'main' into nv/v2-dashboard-create 2026-05-26 12:03:27 +05:30
Naman Verma
8fbec89aab chore: remove enum def of threshold comparison operator 2026-05-26 12:02:18 +05:30
Naman Verma
40da02e5b4 test: add unit tests for type conversions 2026-05-26 12:01:02 +05:30
Naman Verma
6704e15a01 fix: correct convertor method name 2026-05-26 11:29:48 +05:30
Naman Verma
d05697fdba fix: fix post merge build and spec errors 2026-05-26 11:13:07 +05:30
Naman Verma
fd3ddab9fd Merge branch 'nv/v2-list-dashboard' into nv/v2-dashboard-delete 2026-05-26 11:11:36 +05:30
Naman Verma
1b3b891d5c chore: bump migration number 2026-05-26 11:07:51 +05:30
Naman Verma
d903f6cb1b Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-05-26 11:07:27 +05:30
Naman Verma
fbf30cc192 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-update 2026-05-26 11:06:47 +05:30
Naman Verma
ecd9670bf6 Merge branch 'main' into nv/v2-dashboard-create 2026-05-26 11:05:56 +05:30
Naman Verma
b5fb45904c fix: add quotes around tag relation kind 2026-05-25 16:07:25 +05:30
Naman Verma
c24caf0e9a fix: dont include full data in list response 2026-05-25 15:51:25 +05:30
Naman Verma
dc42fae378 fix: fix tests based on name related changes 2026-05-25 15:42:08 +05:30
Naman Verma
94e70179d7 chore: generate api specs 2026-05-25 15:28:21 +05:30
Naman Verma
063c104b80 chore: bump migration number 2026-05-25 15:26:11 +05:30
Naman Verma
937ebfd843 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-05-25 15:25:47 +05:30
Naman Verma
d8da99fcbb test: fix build errors and tests based on name related changes 2026-05-25 15:24:08 +05:30
Naman Verma
075e8d3029 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-update 2026-05-25 14:39:27 +05:30
Naman Verma
f7fc3eade8 feat: add validation on dashboard name 2026-05-25 14:32:45 +05:30
Naman Verma
9415c06166 Merge branch 'main' into nv/v2-dashboard-create 2026-05-25 14:12:27 +05:30
Naman Verma
fa8139b279 chore: remove integration test for now (will add along with list api) 2026-05-25 14:12:05 +05:30
Naman Verma
3271e35eeb fix: set display name in unmarshal json 2026-05-25 14:10:34 +05:30
Naman Verma
a48beef8ec chore: increase MaxTagsPerDashboard to 10 2026-05-25 14:06:15 +05:30
Naman Verma
ad4e3dcf45 fix: remove unneeded comment 2026-05-25 14:05:15 +05:30
Naman Verma
2e8295f0b3 fix: improve api descriptions 2026-05-25 14:03:32 +05:30
Naman Verma
3e6394ba50 fix: remove unused param in constructor 2026-05-25 13:59:53 +05:30
Naman Verma
95da13ecb9 feat: add immutable name in dashboard v2 api specs 2026-05-25 13:58:01 +05:30
Naman Verma
8b38e3969f Merge branch 'main' into nv/v2-dashboard-create 2026-05-25 13:56:54 +05:30
Naman Verma
f3d097a61a feat: add immutable name in dashboard v2 2026-05-25 13:55:18 +05:30
Naman Verma
81249342ff feat: add immutable name in dashboard v2 2026-05-25 13:55:08 +05:30
Naman Verma
5ce097ed2a fix: add some required fields 2026-05-25 12:51:06 +05:30
Naman Verma
ad9b17d9ae fix: remove system dashboards from list v2 response 2026-05-25 11:47:12 +05:30
Naman Verma
6d5eed3782 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-05-25 11:39:16 +05:30
Naman Verma
3c985af24c Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-update 2026-05-25 11:38:56 +05:30
Naman Verma
c3f50b5db8 chore: incorporate source 2026-05-25 11:38:35 +05:30
Naman Verma
000dcc10b2 chore: incorporate source in api spec 2026-05-25 11:33:15 +05:30
Naman Verma
d7ffbe3f9f chore: incorporate source 2026-05-25 11:31:03 +05:30
Naman Verma
72cbc0450b Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-update 2026-05-25 11:14:19 +05:30
Naman Verma
7d7fd425e2 fix: add source for v2 dashboards 2026-05-25 11:13:48 +05:30
Naman Verma
e3e2ea61a0 fix: send total count in response + bug fixes 2026-05-25 11:10:03 +05:30
Naman Verma
a870a98a80 chore: bump migration number 2026-05-25 10:23:19 +05:30
Naman Verma
d55846af03 Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-05-25 10:22:49 +05:30
Naman Verma
55cfe57a9e Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-update 2026-05-25 10:22:04 +05:30
Naman Verma
0a4c75d899 Merge branch 'main' into nv/v2-dashboard-create 2026-05-25 10:21:47 +05:30
Naman Verma
44292bdc1d fix: remove hasMore from list response 2026-05-25 10:21:21 +05:30
Naman Verma
5e6b71e56e fix: add missing request struct in list api 2026-05-22 18:57:51 +05:30
Naman Verma
abeab11271 fix: update migration numbering 2026-05-21 17:11:25 +05:30
Naman Verma
26b007cce5 chore: generate api specs 2026-05-21 10:40:25 +05:30
Naman Verma
0d1766b7c2 fix: fix build errors 2026-05-21 10:18:12 +05:30
Naman Verma
61c15c704b Merge branch 'nv/v2-dashboard-update' into nv/v2-list-dashboard 2026-05-21 10:14:38 +05:30
Naman Verma
6702930688 feat: patch dashboard api (#11182)
* feat: lock, unlock, create public, update public v2 dashboard APIs

* feat: delete dashboard v2 API and hard delete cron job

* feat: patch dashboard api

* chore: update api specs

* chore: update api specs

* chore: update api specs

* chore: remove delete related work

* fix: add examples of structs for value param in param description

* test: unit test fixes

* fix: use new pattern of checking for admin permission

* fix: remove soft delete reference

* test: key value tags in test

* fix: build error in patch module method

* fix: build error in Apply method

* fix: use sync tags method in update

* fix: fix build errors

* fix: fix all patch application tests

* chore: add more mapper methods
2026-05-21 10:04:38 +05:30
Naman Verma
4a24ee44e8 chore: generate api specs 2026-05-20 23:44:22 +05:30
Naman Verma
0045ecadab Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-update 2026-05-20 23:43:34 +05:30
Naman Verma
69aad53eb4 chore: generate api specs 2026-05-20 23:42:24 +05:30
Naman Verma
7bacb03483 Merge branch 'main' into nv/v2-dashboard-create 2026-05-20 23:41:18 +05:30
Naman Verma
b610056954 chore: update frontend schema 2026-05-18 20:34:45 +05:30
Naman Verma
db77b398e7 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-update 2026-05-18 20:34:17 +05:30
Naman Verma
574867bafb chore: update frontend schema 2026-05-18 20:33:38 +05:30
Naman Verma
d87edca9d1 Merge branch 'main' into nv/v2-dashboard-create 2026-05-18 20:32:34 +05:30
Naman Verma
a7debaa6ed feat: lock, unlock, create public, update public v2 dashboard APIs (#11167)
* feat: lock, unlock, create public, update public v2 dashboard APIs

* chore: update api specs

* fix: use new pattern of checking for admin permission

* fix: remove soft delete reference

* chore: revert all frontend changes

* fix: fix build errors and remove v2 create/update public apis

* chore: use v1 methods wherever possible

* fix: use update v2 store method
2026-05-15 13:12:04 +05:30
Naman Verma
85ac805fae fix: fix build errors post merge conflict resolution 2026-05-15 12:21:25 +05:30
Naman Verma
8a0441293a chore: revert all frontend changes 2026-05-15 12:17:44 +05:30
Naman Verma
25ae787ecb Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-update 2026-05-15 12:15:58 +05:30
Naman Verma
f0ed0a8967 feat: v2 dashboard GET API (#11136)
* feat: v2 dashboard GET API

* Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get

* chore: update api specs

* fix: remove soft delete references

* chore: embed StorableDashboard into joinedRow in store method

* fix: fix build error

* chore: revert all frontend changes

* fix: remove public dashboard from get v2 call
2026-05-15 11:26:02 +05:30
Naman Verma
b47343bc09 Merge branch 'main' into nv/v2-dashboard-create 2026-05-15 10:52:06 +05:30
Naman Verma
bb39c52229 feat: follow the metadata+spec key structure in open api spec 2026-05-15 00:36:49 +05:30
Naman Verma
5fe69473c9 feat: follow the metadata+spec key structure 2026-05-15 00:34:12 +05:30
Naman Verma
9be77ace42 chore: change dashboardData to dashboardSpec 2026-05-14 20:23:54 +05:30
Naman Verma
c804d8f9b6 fix: remove sqlstore passage in ee pkg 2026-05-14 20:21:12 +05:30
Naman Verma
996bd949f2 fix: add ctx needed in sqlstore 2026-05-14 20:20:44 +05:30
Naman Verma
84225023a5 chore: rename module to m 2026-05-14 20:04:25 +05:30
Naman Verma
a5b9dd279c chore: move NewDashboardV2 to NewDashboardV2WithoutTags 2026-05-14 20:03:44 +05:30
Naman Verma
172418a337 fix: use binding package to get request 2026-05-14 20:02:46 +05:30
Naman Verma
d1d5a9fa32 fix: use store.RunInTx instead of taking in sqlstore 2026-05-14 20:01:18 +05:30
Naman Verma
6f81e9f364 chore: revert idx generation to resolve conflicts 2026-05-14 19:50:09 +05:30
Naman Verma
1933bec786 Merge branch 'main' into nv/v2-dashboard-create 2026-05-14 19:48:57 +05:30
Naman Verma
6d3d9bfb49 Merge branch 'main' into nv/v2-dashboard-create 2026-05-14 19:46:00 +05:30
Naman Verma
8b89f4af85 chore: remove uploaded grafana flag from metadata 2026-05-14 17:51:10 +05:30
Naman Verma
66e4132504 Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-14 17:49:37 +05:30
Naman Verma
29e14ce9c6 chore: bump migration number 2026-05-14 17:47:34 +05:30
Naman Verma
d7dc789a58 Merge branch 'main' into nv/tags 2026-05-14 17:45:55 +05:30
Naman Verma
d2d129eea9 chore: comment out unique index test 2026-05-14 16:03:21 +05:30
Naman Verma
9e1704615f feat: add created at to tag relations 2026-05-14 15:58:52 +05:30
Naman Verma
db06557c12 chore: comment out unique index test 2026-05-14 15:51:18 +05:30
Naman Verma
1475e2b53a chore: add a todo comment 2026-05-14 15:37:07 +05:30
Naman Verma
e58119a416 chore: comment out tags unique index for now 2026-05-14 15:27:53 +05:30
Naman Verma
4374f75394 feat: delete dashboard v2 API 2026-05-13 23:06:20 +05:30
Naman Verma
937a469e80 Merge branch 'nv/patch-dashboard' into nv/v2-list-dashboard 2026-05-13 22:44:21 +05:30
Naman Verma
b0262a7d89 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-13 22:43:43 +05:30
Naman Verma
18200c049e Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-13 22:43:14 +05:30
Naman Verma
3dc5b53cc3 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-13 22:42:32 +05:30
Naman Verma
60fef93100 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-13 22:41:57 +05:30
Naman Verma
f5935ccaf4 Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-13 22:41:17 +05:30
Naman Verma
d354044fbe Merge branch 'main' into nv/tags 2026-05-13 22:40:29 +05:30
Naman Verma
fe6cbc3c0c test: add unit tests for new idx type 2026-05-13 22:39:01 +05:30
Naman Verma
45fa0c739c chore: move tag resolution to module 2026-05-13 17:35:24 +05:30
Naman Verma
e1527dd148 chore: combine functional unique index with unique index 2026-05-13 17:23:22 +05:30
Naman Verma
5063be6467 chore: use tagtypestest package for mock store 2026-05-13 17:09:09 +05:30
Naman Verma
b453655dea chore: rename create method to createOrGet 2026-05-13 16:54:58 +05:30
Naman Verma
8e4521177d fix: correct the method name being called 2026-05-13 14:39:32 +05:30
Naman Verma
edf2c4493b Merge branch 'nv/patch-dashboard' into nv/v2-list-dashboard 2026-05-13 14:37:21 +05:30
Naman Verma
39174d6040 fix: use sync tags method in update 2026-05-13 14:36:57 +05:30
Naman Verma
2e7500a0b2 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-13 14:32:16 +05:30
Naman Verma
5987228e4a Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-13 14:30:09 +05:30
Naman Verma
08a59145cc fix: use sync tags method in update 2026-05-13 14:29:08 +05:30
Naman Verma
744856680e Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-13 14:23:07 +05:30
Naman Verma
681e205ac1 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-13 14:22:38 +05:30
Naman Verma
4cb27e330e Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-13 14:22:20 +05:30
Naman Verma
b47bc6bcc4 fix: only ascii in regex 2026-05-13 13:51:02 +05:30
Naman Verma
5321e9ee87 feat: functional unique index in sql schema 2026-05-13 13:27:44 +05:30
Naman Verma
dd615869f6 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-13 12:21:59 +05:30
Naman Verma
fc4f326953 fix: use sync tags in create api 2026-05-13 12:21:45 +05:30
Naman Verma
2af4cbf0f9 Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-13 12:11:30 +05:30
Naman Verma
e82f568a27 chore: remove methods that shouldn't be exposed 2026-05-13 12:11:09 +05:30
Naman Verma
c2aac3a278 fix: add regex for tags 2026-05-13 12:00:26 +05:30
Naman Verma
ddfec3e5f7 fix: add len check on tags keys and values 2026-05-13 11:32:31 +05:30
Naman Verma
af623f66e8 chore: bump migration number 2026-05-13 11:23:03 +05:30
Naman Verma
1cdecceece fix: fix build error 2026-05-13 11:00:52 +05:30
Naman Verma
fb4f0a9c63 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-13 10:59:55 +05:30
Naman Verma
268e747f5c fix: fix build error 2026-05-13 10:59:12 +05:30
Naman Verma
3fc72329c9 Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-13 10:56:15 +05:30
Naman Verma
10ecb7524c fix: add ID in tag relation 2026-05-13 10:33:27 +05:30
Naman Verma
8fc21ca6b9 fix: remove user auditable 2026-05-13 10:19:51 +05:30
Naman Verma
422149369d fix: add org id filter in all list and delete queries 2026-05-13 10:11:55 +05:30
Naman Verma
2063697350 chore: change entity id to resource id 2026-05-13 09:41:01 +05:30
Naman Verma
685faa9211 Merge branch 'main' into nv/tags 2026-05-13 09:23:28 +05:30
Naman Verma
de6fcb9fbb chore: bump migration number 2026-05-12 14:19:47 +05:30
Naman Verma
828619a9e6 Merge branch 'main' into nv/tags 2026-05-12 14:19:17 +05:30
Naman Verma
aff2e1be6b fix: fix build errors in dashboard module 2026-05-12 14:13:40 +05:30
Naman Verma
9728d17a0a fix: remove entity type definition 2026-05-12 14:10:29 +05:30
Naman Verma
ffaf334dfd Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-12 14:09:58 +05:30
Naman Verma
d87e7241c0 feat: add SyncTags method that covers creation and linking 2026-05-12 14:09:19 +05:30
Naman Verma
ae184315a9 feat: foreign key on tag id 2026-05-12 13:55:33 +05:30
Naman Verma
29f782a3a0 chore: remove org ID from tag relation 2026-05-12 13:51:14 +05:30
Naman Verma
71d8dafce1 fix: singular table name 2026-05-12 13:45:02 +05:30
Naman Verma
412320d7d9 fix: use coretypes.Kind instead of defining entity type 2026-05-12 13:42:35 +05:30
Naman Verma
9b5d78b5a0 Merge branch 'main' into nv/tags 2026-05-12 13:21:20 +05:30
Naman Verma
444464ae15 fix: created and updated by schema 2026-05-12 13:04:48 +05:30
Naman Verma
d5841f8daa fix: correct pk in bun model for tag relations 2026-05-12 12:29:29 +05:30
Naman Verma
c344cd256f fix: diff error codes for invalid keys and values 2026-05-12 12:27:46 +05:30
Naman Verma
563b289469 fix: visitor should follow new tag struct 2026-05-12 00:21:13 +05:30
Naman Verma
eae3ff7ee6 chore: embed StorableDashboard in listedRow 2026-05-12 00:07:31 +05:30
Naman Verma
f228f2c9bf fix: build error fix 2026-05-12 00:03:50 +05:30
Naman Verma
44afbbe122 fix: remove soft delete references 2026-05-11 23:52:14 +05:30
Naman Verma
771ae521ab chore: remove newline 2026-05-11 23:49:17 +05:30
Naman Verma
7b97979d84 Merge branch 'nv/patch-dashboard' into nv/v2-list-dashboard 2026-05-11 23:47:58 +05:30
Naman Verma
4c604c3079 fix: build error in Apply method 2026-05-11 23:43:58 +05:30
Naman Verma
a11bc8c325 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-11 23:43:30 +05:30
Naman Verma
35930198d0 Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-11 23:43:14 +05:30
Naman Verma
48f4838b93 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-11 23:42:55 +05:30
Naman Verma
ce7735d348 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-11 23:42:37 +05:30
Naman Verma
2f6b7b6260 Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-11 23:42:21 +05:30
Naman Verma
5e61be1606 feat: method to build postable tags from tags 2026-05-11 23:42:08 +05:30
Naman Verma
124b529392 fix: build error in patch module method 2026-05-11 23:41:18 +05:30
Naman Verma
27f334dbe0 test: key value tags in test 2026-05-11 23:40:49 +05:30
Naman Verma
b626d4b868 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-11 23:25:59 +05:30
Naman Verma
3019d151ae fix: remove soft delete reference 2026-05-11 23:08:06 +05:30
Naman Verma
7fa00ef30b fix: use new pattern of checking for admin permission 2026-05-11 23:05:27 +05:30
Naman Verma
1abf66f593 Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-11 22:51:44 +05:30
Naman Verma
d8f7e62565 fix: remove soft delete references 2026-05-11 22:47:02 +05:30
Naman Verma
9380569223 fix: compile error fix 2026-05-11 22:42:40 +05:30
Naman Verma
1605b1c1ec Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-11 22:36:00 +05:30
Naman Verma
42660ca8a6 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-11 22:34:22 +05:30
Naman Verma
1f7032953c Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-11 22:34:02 +05:30
Naman Verma
173037d3be fix: extend bun in joinedRow 2026-05-11 22:33:37 +05:30
Naman Verma
e9aab5a618 chore: embed StorableDashboard into joinedRow in store method 2026-05-11 22:32:35 +05:30
Naman Verma
c0113324ca fix: remove soft delete references 2026-05-11 22:17:11 +05:30
Naman Verma
6d59fa4700 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-11 22:13:40 +05:30
Naman Verma
4713fd4839 chore: generate api spec 2026-05-11 14:47:02 +05:30
Naman Verma
4ad872b722 fix: add back api endpoint 2026-05-11 14:24:53 +05:30
Naman Verma
642fb66831 Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-11 14:11:28 +05:30
Naman Verma
d12c846212 chore: change where tag module is instantiated 2026-05-11 14:10:07 +05:30
Naman Verma
4e5bd7cf6f fix: lint fix regarding nil, nil return in test file 2026-05-11 14:06:08 +05:30
Naman Verma
3982cce603 chore: merge conflicts error fixing pt 1 2026-05-11 13:59:41 +05:30
Naman Verma
1a43c85cb8 Merge branch 'nv/tags' into nv/v2-dashboard-create 2026-05-11 13:53:21 +05:30
Naman Verma
bd11e985e1 feat: new module for tags 2026-05-11 13:45:10 +05:30
Naman Verma
4f82bae07a Merge branch 'nv/patch-dashboard' into nv/v2-list-dashboard 2026-05-08 18:12:44 +05:30
Naman Verma
aa6066f7a8 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-08 18:12:41 +05:30
Naman Verma
128abf413e Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-08 18:12:39 +05:30
Naman Verma
abd7e41f97 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-08 18:12:33 +05:30
Naman Verma
3ebde75ebd Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-08 18:11:59 +05:30
Naman Verma
3e849ee2d3 feat: reserved DSL key validation for tags 2026-05-08 18:10:28 +05:30
Naman Verma
f7d9a57637 fix: pass entity type in create many 2026-05-08 17:56:58 +05:30
Naman Verma
629779a666 Merge branch 'nv/patch-dashboard' into nv/v2-list-dashboard 2026-05-08 15:49:12 +05:30
Naman Verma
c864faf01f Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-08 15:49:09 +05:30
Naman Verma
99802daa3d Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-08 15:49:06 +05:30
Naman Verma
7dfa474dc1 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-08 15:49:04 +05:30
Naman Verma
5c223e9b04 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-08 15:49:01 +05:30
Naman Verma
fceb770337 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-08 15:48:58 +05:30
Naman Verma
44496d9d8d feat: entity type column in tags 2026-05-08 15:48:51 +05:30
Naman Verma
24d3f65200 Merge branch 'nv/patch-dashboard' into nv/v2-list-dashboard 2026-05-08 13:38:36 +05:30
Naman Verma
9ceaaeecf1 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-08 13:38:33 +05:30
Naman Verma
3e9c1fd7c9 Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-08 13:38:30 +05:30
Naman Verma
3b0fa192d8 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-08 13:38:28 +05:30
Naman Verma
8c44c42e13 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-08 13:38:25 +05:30
Naman Verma
398943fe41 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-08 13:38:22 +05:30
Naman Verma
a17debc61b feat: move tags to key:value pairs model 2026-05-08 13:38:13 +05:30
Naman Verma
c13270814a feat: v2 list dashboards api 2026-05-07 19:12:58 +05:30
Naman Verma
7eb6dbe4a6 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-07 12:06:44 +05:30
Naman Verma
ce424b776b Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-07 12:06:41 +05:30
Naman Verma
0fae729715 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-07 12:06:39 +05:30
Naman Verma
6079e9869c Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-07 12:06:36 +05:30
Naman Verma
3113b82904 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-07 12:06:33 +05:30
Naman Verma
71c60c3f2a test: fix mock interface in test 2026-05-07 12:06:27 +05:30
Naman Verma
abfc19e27a Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-07 02:25:20 +05:30
Naman Verma
762a852a4f Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-07 02:25:17 +05:30
Naman Verma
3cc2a689c8 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-07 02:25:14 +05:30
Naman Verma
b74f5854fc Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-07 02:25:11 +05:30
Naman Verma
3b824d50a3 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-07 02:25:09 +05:30
Naman Verma
d0a693b034 feat: method to fetch tags for multiple entries at once 2026-05-07 02:24:42 +05:30
Naman Verma
ee4508cb85 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-07 02:19:04 +05:30
Naman Verma
b2aec2edaf Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-07 02:19:01 +05:30
Naman Verma
cd7899795d Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-07 02:18:59 +05:30
Naman Verma
ad2d1467ec Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-07 02:18:56 +05:30
Naman Verma
90377f8116 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-07 02:18:54 +05:30
Naman Verma
cabfd7271b Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-05-07 02:18:50 +05:30
Naman Verma
750d63cf6b test: unit test fixes 2026-05-07 02:16:05 +05:30
Naman Verma
d0bfee2645 test: unit test fixes 2026-05-07 02:15:21 +05:30
Naman Verma
8b505c0197 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/patch-dashboard 2026-05-06 11:25:46 +05:30
Naman Verma
431fb7ca62 Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-06 11:25:44 +05:30
Naman Verma
44cf8ed8e7 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-06 11:25:41 +05:30
Naman Verma
4d1129c85f Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-06 11:25:39 +05:30
Naman Verma
e4c4acb5df Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-06 11:25:36 +05:30
Naman Verma
c9235cd3d2 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-05-06 11:22:25 +05:30
Naman Verma
ec837c7006 fix: allow only 1 query in a panel 2026-05-06 11:21:49 +05:30
Naman Verma
cbba2e16d8 fix: add examples of structs for value param in param description 2026-05-05 17:58:58 +05:30
Naman Verma
1d0ab788d5 chore: remove delete related work 2026-05-05 17:51:52 +05:30
Naman Verma
4aae71462b chore: update api specs 2026-05-05 16:57:03 +05:30
Naman Verma
bd49d94144 Merge branch 'nv/delete-v2-dashboard' into nv/patch-dashboard 2026-05-05 16:55:47 +05:30
Naman Verma
fa7205a673 chore: update api specs 2026-05-05 16:55:30 +05:30
Naman Verma
13ec049495 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/delete-v2-dashboard 2026-05-05 16:54:49 +05:30
Naman Verma
3e8468ab23 chore: update api specs 2026-05-05 16:54:34 +05:30
Naman Verma
e6cb7fabde Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-05 16:53:52 +05:30
Naman Verma
59b8fa0e05 chore: update api specs 2026-05-05 16:53:34 +05:30
Naman Verma
133a3a0057 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-05 16:52:12 +05:30
Naman Verma
b4e524dae0 chore: update api specs 2026-05-05 16:51:56 +05:30
Naman Verma
2aa46f9f86 Merge branch 'nv/delete-v2-dashboard' into nv/patch-dashboard 2026-05-05 16:41:55 +05:30
Naman Verma
73fa15da83 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/delete-v2-dashboard 2026-05-05 16:41:43 +05:30
Naman Verma
cd70d0bdeb Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-05 16:41:32 +05:30
Naman Verma
4de0092664 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-05 16:41:18 +05:30
Naman Verma
337d23c91f Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get
# Conflicts:
#	pkg/modules/tag/impltag/module.go
#	pkg/modules/tag/tag.go
#	pkg/types/tagtypes/tag_test.go
2026-05-05 16:41:04 +05:30
Naman Verma
a1f73655ca Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-05 16:39:35 +05:30
Naman Verma
0d6081d0d0 feat: consolidate tag module and tagtypes changes from downstream branches 2026-05-05 16:39:13 +05:30
Naman Verma
301d0103b0 Merge branch 'nv/delete-v2-dashboard' into nv/patch-dashboard 2026-05-05 11:59:12 +05:30
Naman Verma
dc99772ee4 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/delete-v2-dashboard 2026-05-05 11:58:45 +05:30
Naman Verma
80849ebfeb Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-05 11:58:22 +05:30
Naman Verma
2c0c7240a4 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-05 11:56:39 +05:30
Naman Verma
28cb0a8be7 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-05 11:54:54 +05:30
Naman Verma
54832cad34 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-05 11:54:38 +05:30
Naman Verma
a45178d709 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-05-05 11:54:21 +05:30
Naman Verma
c4224ecf72 Merge branch 'main' into nv/dashboardv2 2026-05-05 11:53:56 +05:30
Naman Verma
14927c89d3 feat: patch dashboard api 2026-05-05 09:22:25 +05:30
Naman Verma
55487dde3a Merge branch 'nv/other-dashboard-v2-update-methods' into nv/delete-v2-dashboard 2026-05-04 19:27:40 +05:30
Naman Verma
fc5717af51 Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-04 19:27:26 +05:30
Naman Verma
8bf650192e Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-04 19:27:14 +05:30
Naman Verma
f8fb7e5f8d Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-04 19:27:02 +05:30
Naman Verma
ff578f7d92 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-04 19:26:49 +05:30
Naman Verma
cd630b1152 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-05-04 19:26:36 +05:30
Naman Verma
bd0842ac17 fix: query-less panels not allowed 2026-05-04 19:25:49 +05:30
Naman Verma
b3e3dd13b4 Merge branch 'nv/other-dashboard-v2-update-methods' into nv/delete-v2-dashboard 2026-05-04 17:48:42 +05:30
Naman Verma
710d5531f3 Merge branch 'nv/v2-dashboard-update' into nv/other-dashboard-v2-update-methods 2026-05-04 17:45:15 +05:30
Naman Verma
e37e427079 fix: merge fixes 2026-05-04 17:40:46 +05:30
Naman Verma
1e99ab4659 Merge branch 'nv/v2-dashboard-get' into nv/v2-dashboard-update 2026-05-04 17:40:26 +05:30
Naman Verma
3353cda021 Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-04 17:35:33 +05:30
Naman Verma
f5a71037bf Merge branch 'nv/v2-dashboard-create' into nv/v2-dashboard-get 2026-05-04 17:33:03 +05:30
Naman Verma
97b85c386a fix: no v2 package and its consequences 2026-05-04 17:27:58 +05:30
Naman Verma
00bdf50c1c fix: no v2 package and its consequences 2026-05-04 17:26:12 +05:30
Naman Verma
5dec4ec580 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-04 17:18:39 +05:30
Naman Verma
325767c240 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-05-04 17:17:32 +05:30
Naman Verma
5fed2a4585 chore: no v2 subpackage 2026-05-04 17:16:39 +05:30
Naman Verma
664337ae0f Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-05-04 16:19:29 +05:30
Naman Verma
a0ea276681 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-05-04 16:18:03 +05:30
Naman Verma
2dc8699f08 fix: wrap errors 2026-05-04 14:55:38 +05:30
Naman Verma
ed81ed8ab5 fix: no need for copying textboxvariablespec 2026-05-04 14:44:42 +05:30
Naman Verma
48c9da19df fix: return 500 err if spec is nil for composite kind w/ code comment 2026-05-04 14:34:16 +05:30
Naman Verma
eb9663d518 fix: remove extra (un)marshal cycle 2026-05-04 14:18:37 +05:30
Naman Verma
a56a862338 fix: add allowed values in err messages 2026-05-04 14:16:22 +05:30
Naman Verma
021f33f65e Merge branch 'main' into nv/dashboardv2 2026-05-04 12:52:31 +05:30
Naman Verma
ca96c71146 feat: delete dashboard v2 API and hard delete cron job 2026-05-03 15:01:43 +05:30
Naman Verma
de2909d1d1 feat: lock, unlock, create public, update public v2 dashboard APIs 2026-05-03 14:38:24 +05:30
Naman Verma
f311fcabf7 feat: v2 dashboard update API 2026-04-29 18:39:48 +05:30
Naman Verma
a37c07f881 feat: v2 dashboard GET API 2026-04-29 15:12:47 +05:30
Naman Verma
4d9386f418 fix: merge conflicts 2026-04-29 14:36:39 +05:30
Naman Verma
737473521d Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-04-29 14:33:25 +05:30
Naman Verma
1863db8ba8 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-29 14:32:14 +05:30
Naman Verma
661af09a13 Merge branch 'main' into nv/dashboardv2 2026-04-29 14:31:59 +05:30
Naman Verma
6024fa2b91 fix: remove extra spec from builder query marshalling 2026-04-29 14:31:16 +05:30
Naman Verma
8996a96387 chore: use existing mapper 2026-04-29 14:09:34 +05:30
Naman Verma
d6db5c2aab test: integration test fixes 2026-04-29 12:56:14 +05:30
Naman Verma
709590ea1b test: integration tests for create API 2026-04-29 12:23:12 +05:30
Naman Verma
1add46b4c5 fix: module should also validate postable dashboard 2026-04-28 20:05:38 +05:30
Naman Verma
8401261e20 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-04-28 20:04:33 +05:30
Naman Verma
0ff34a7274 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-28 20:04:08 +05:30
Naman Verma
44e3bd9608 chore: separate method for validation 2026-04-28 20:03:48 +05:30
Naman Verma
c3944d779e fix: more dashboard request validations 2026-04-28 19:59:11 +05:30
Naman Verma
f5ec783a53 fix: go lint fix 2026-04-28 19:33:28 +05:30
Naman Verma
35b729c425 Merge branch 'nv/tags-for-dashboard-create' into nv/v2-dashboard-create 2026-04-28 19:30:42 +05:30
Naman Verma
4f43c3d803 fix: use existing tag's casing if new tag is a prefix of an existing tag 2026-04-28 19:30:07 +05:30
Naman Verma
5dbde6c64d fix: only return name of a tag in dashboard response 2026-04-28 19:13:03 +05:30
Naman Verma
fb6fdd54ec feat: v2 create dashboard API 2026-04-28 15:05:29 +05:30
Naman Verma
64b8ba62da Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-28 15:04:11 +05:30
Naman Verma
7c66df408b Merge branch 'main' into nv/dashboardv2 2026-04-28 15:04:03 +05:30
Naman Verma
54049de391 chore: follow proper unmarshal json method structure 2026-04-28 15:02:49 +05:30
Naman Verma
a82f4237c8 Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-28 09:52:23 +05:30
Naman Verma
89606b6238 Merge branch 'main' into nv/dashboardv2 2026-04-28 09:52:13 +05:30
Naman Verma
db5ce958eb Merge branch 'nv/dashboardv2' into nv/tags-for-dashboard-create 2026-04-28 09:49:01 +05:30
Naman Verma
c8d3a9a54b feat: enum for entity type that other modules can register 2026-04-28 09:47:24 +05:30
Naman Verma
637870b1fc feat: define tags module for v2 dashboard creation 2026-04-27 22:14:47 +05:30
Naman Verma
d46a7e24c9 Merge branch 'main' into nv/dashboardv2 2026-04-27 22:12:12 +05:30
Naman Verma
2a451e1c31 test: test for drift detection mechanics 2026-04-27 18:57:41 +05:30
Naman Verma
60b6d1d890 chore: better method name extractKindAndSpec 2026-04-27 18:42:31 +05:30
Naman Verma
36f755b232 chore: cleanup comments 2026-04-27 18:39:41 +05:30
Naman Verma
c1b3e3683a chore: code movement 2026-04-27 18:37:57 +05:30
Naman Verma
4c68544b1a chore: go lint fix (godot) 2026-04-27 18:37:05 +05:30
Naman Verma
90d9ab95f9 chore: code movement 2026-04-27 18:35:18 +05:30
Naman Verma
065e712e0c chore: code movement 2026-04-27 18:33:48 +05:30
Naman Verma
50db309ecd chore: code movement 2026-04-27 18:32:41 +05:30
Naman Verma
261bc552b0 chore: cleanup testing code 2026-04-27 18:24:52 +05:30
Naman Verma
bab720e98b Merge branch 'main' into nv/dashboardv2 2026-04-27 18:21:09 +05:30
Naman Verma
71fef6636b chore: better method name 2026-04-27 18:18:14 +05:30
Naman Verma
fc3cdecbbb chore: cleaner comment 2026-04-27 18:15:21 +05:30
Naman Verma
860fcfa641 chore: cleaner comment 2026-04-27 18:14:27 +05:30
Naman Verma
a090e3a4aa chore: cleaner comment 2026-04-27 18:14:02 +05:30
Naman Verma
6cf73e2ade chore: better comment to explain what restrictKindToLiteral does 2026-04-27 18:13:34 +05:30
Naman Verma
bbcb6a45d6 chore: renames and code rearrangement 2026-04-27 17:53:54 +05:30
Naman Verma
d13934febc fix: remove textbox plugin from openapi spec 2026-04-27 17:29:36 +05:30
Naman Verma
d5a7b7523d fix: strict decode variable spec as well 2026-04-27 17:27:51 +05:30
Naman Verma
5b8984f131 Merge branch 'main' into nv/dashboardv2 2026-04-27 17:18:44 +05:30
Naman Verma
6ddc5f1f12 chore: better error messages 2026-04-27 17:18:11 +05:30
Naman Verma
055968bfad fix: dot at the end of a comment 2026-04-27 17:07:58 +05:30
Naman Verma
1bf0f38ed9 fix: js lint errors 2026-04-27 17:07:38 +05:30
Naman Verma
842125e20a chore: too many comments 2026-04-27 16:50:41 +05:30
Naman Verma
6dab35caf8 chore: better file name 2026-04-27 16:43:42 +05:30
Naman Verma
047e9e2001 chore: better file names 2026-04-27 16:42:31 +05:30
Naman Verma
45eaa7db58 test: add tests for spec wrappers 2026-04-27 16:36:27 +05:30
Naman Verma
8a3d894eba chore: comment cleanup 2026-04-27 16:32:29 +05:30
Naman Verma
5239060b53 chore: move plugin maps to correct file 2026-04-27 16:30:33 +05:30
Naman Verma
42c6f507ac test: more descriptive test file name 2026-04-27 15:42:54 +05:30
Naman Verma
1b695a0b80 chore: separate file for perses replicas 2026-04-27 15:42:21 +05:30
Naman Verma
438cfab155 chore: comment clean up 2026-04-27 15:39:46 +05:30
Naman Verma
69f7617e01 Merge branch 'main' into nv/dashboardv2 2026-04-27 15:36:58 +05:30
Naman Verma
4420a7e1fc test: much bigger json for data column 2026-04-24 22:16:03 +05:30
Naman Verma
b4bc68c5c5 test: data column in perf tests should match real data 2026-04-24 17:17:37 +05:30
Naman Verma
eb9eb317cc test: perf test script for both sql flavours 2026-04-23 17:14:33 +05:30
Naman Verma
0b1eb16a42 test: fixes in dashboard perf testing data generator 2026-04-23 15:42:58 +05:30
Naman Verma
05a4d12183 test: script to generate test dashboard data in a sql db 2026-04-23 14:19:58 +05:30
Naman Verma
bbaf64c4f0 feat: openapi spec generation 2026-04-21 13:41:06 +05:30
245 changed files with 9462 additions and 6287 deletions

View File

@@ -39,7 +39,6 @@ jobs:
matrix:
suite:
- alerts
- basepath
- callbackauthn
- cloudintegrations
- dashboard
@@ -84,7 +83,7 @@ jobs:
run: |
cd tests && uv sync
- name: webdriver
if: matrix.suite == 'callbackauthn' || matrix.suite == 'basepath'
if: matrix.suite == 'callbackauthn'
run: |
wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | sudo tee -a /etc/apt/sources.list.d/google-chrome.list

View File

@@ -91,7 +91,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
sqlstoreProviderFactories(),
signoz.NewTelemetryStoreProviderFactories(),
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
return signoz.NewAuthNs(ctx, providerSettings, store, licensing, config.Global)
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
},
func(ctx context.Context, sqlstore sqlstore.SQLStore, config authz.Config, _ licensing.Licensing, _ []authz.OnBeforeRoleDelete) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore, config)

View File

@@ -107,17 +107,17 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
sqlstoreProviderFactories(),
signoz.NewTelemetryStoreProviderFactories(),
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
samlCallbackAuthN, err := samlcallbackauthn.New(ctx, store, licensing, config.Global)
samlCallbackAuthN, err := samlcallbackauthn.New(ctx, store, licensing)
if err != nil {
return nil, err
}
oidcCallbackAuthN, err := oidccallbackauthn.New(store, licensing, providerSettings, config.Global)
oidcCallbackAuthN, err := oidccallbackauthn.New(store, licensing, providerSettings)
if err != nil {
return nil, err
}
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing, config.Global)
authNs, err := signoz.NewAuthNs(ctx, providerSettings, store, licensing)
if err != nil {
return nil, err
}

View File

@@ -2583,6 +2583,41 @@ components:
$ref: '#/components/schemas/DashboardtypesVariable'
type: array
type: object
DashboardtypesDashboardView:
properties:
createdAt:
format: date-time
type: string
data:
$ref: '#/components/schemas/DashboardtypesDashboardViewData'
id:
type: string
name:
type: string
orgId:
type: string
updatedAt:
format: date-time
type: string
required:
- id
- name
- data
- orgId
type: object
DashboardtypesDashboardViewData:
properties:
order:
$ref: '#/components/schemas/DashboardtypesListOrder'
query:
type: string
sort:
$ref: '#/components/schemas/DashboardtypesListSort'
version:
type: string
required:
- version
type: object
DashboardtypesDatasourcePlugin:
oneOf:
- $ref: '#/components/schemas/DashboardtypesDatasourcePluginVariantStruct'
@@ -2652,7 +2687,7 @@ components:
$ref: '#/components/schemas/DashboardtypesDashboardSpec'
tags:
items:
$ref: '#/components/schemas/TagtypesPostableTag'
$ref: '#/components/schemas/TagtypesGettableTag'
nullable: true
type: array
updatedAt:
@@ -2770,6 +2805,11 @@ components:
- solid
- dashed
type: string
DashboardtypesListOrder:
enum:
- asc
- desc
type: string
DashboardtypesListPanelSpec:
properties:
selectFields:
@@ -2777,6 +2817,12 @@ components:
$ref: '#/components/schemas/TelemetrytypesTelemetryFieldKey'
type: array
type: object
DashboardtypesListSort:
enum:
- updated_at
- created_at
- name
type: string
DashboardtypesListVariableSpec:
properties:
allowAllValue:
@@ -2799,6 +2845,83 @@ components:
nullable: true
type: string
type: object
DashboardtypesListableDashboardV2:
properties:
dashboards:
items:
$ref: '#/components/schemas/DashboardtypesListedDashboardV2'
type: array
tags:
items:
$ref: '#/components/schemas/TagtypesGettableTag'
type: array
total:
format: int64
type: integer
required:
- dashboards
- total
- tags
type: object
DashboardtypesListableDashboardView:
properties:
views:
items:
$ref: '#/components/schemas/DashboardtypesDashboardView'
type: array
required:
- views
type: object
DashboardtypesListedDashboardV2:
properties:
createdAt:
format: date-time
type: string
createdBy:
type: string
id:
type: string
image:
type: string
locked:
type: boolean
name:
type: string
orgId:
type: string
pinned:
type: boolean
schemaVersion:
type: string
source:
$ref: '#/components/schemas/DashboardtypesSource'
spec:
$ref: '#/components/schemas/DashboardtypesListedDashboardV2Spec'
tags:
items:
$ref: '#/components/schemas/TagtypesGettableTag'
type: array
updatedAt:
format: date-time
type: string
updatedBy:
type: string
required:
- id
- orgId
- locked
- source
- schemaVersion
- name
- pinned
- tags
- spec
type: object
DashboardtypesListedDashboardV2Spec:
properties:
display:
$ref: '#/components/schemas/CommonDisplay'
type: object
DashboardtypesNumberPanelSpec:
properties:
formatting:
@@ -2992,6 +3115,16 @@ components:
- tags
- spec
type: object
DashboardtypesPostableDashboardView:
properties:
data:
$ref: '#/components/schemas/DashboardtypesDashboardViewData'
name:
type: string
required:
- name
- data
type: object
DashboardtypesPostablePublicDashboard:
properties:
defaultTimeRange:
@@ -6998,6 +7131,16 @@ components:
required:
- references
type: object
TagtypesGettableTag:
properties:
key:
type: string
value:
type: string
required:
- key
- value
type: object
TagtypesPostableTag:
properties:
key:
@@ -13033,6 +13176,80 @@ paths:
tags:
- preferences
/api/v2/dashboards:
get:
deprecated: false
description: Returns a page of v2-shape dashboards for the calling user's org.
Supports a filter DSL (`query`), sort (`updated_at`/`created_at`/`name`),
order (`asc`/`desc`), and offset-based pagination (`limit`/`offset`).
operationId: ListDashboardsV2
parameters:
- in: query
name: query
schema:
type: string
- in: query
name: sort
schema:
$ref: '#/components/schemas/DashboardtypesListSort'
- in: query
name: order
schema:
$ref: '#/components/schemas/DashboardtypesListOrder'
- in: query
name: limit
schema:
type: integer
- in: query
name: offset
schema:
type: integer
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/DashboardtypesListableDashboardV2'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List dashboards (v2)
tags:
- dashboard
post:
deprecated: false
description: This endpoint creates a dashboard in the v2 format that follows
@@ -13091,6 +13308,62 @@ paths:
tags:
- dashboard
/api/v2/dashboards/{id}:
delete:
deprecated: false
description: This endpoint deletes a v2-shape dashboard along with its tag relations.
Locked dashboards are rejected.
operationId: DeleteDashboardV2
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- EDITOR
- tokenizer:
- EDITOR
summary: Delete dashboard (v2)
tags:
- dashboard
get:
deprecated: false
description: This endpoint returns a v2-shape dashboard.
@@ -13410,6 +13683,351 @@ paths:
summary: Lock dashboard (v2)
tags:
- dashboard
/api/v2/dashboards/{id}/pins/me:
delete:
deprecated: false
description: Removes the pin for the calling user. Idempotent — unpinning a
dashboard that wasn't pinned still returns 204.
operationId: UnpinDashboardV2
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Unpin a dashboard for the current user (v2)
tags:
- dashboard
put:
deprecated: false
description: Pins the dashboard for the calling user. A user can pin at most
10 dashboards; pinning when at the limit returns 409. Re-pinning an already-pinned
dashboard is a no-op success.
operationId: PinDashboardV2
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Pin a dashboard for the current user (v2)
tags:
- dashboard
/api/v2/dashboards/views:
get:
deprecated: false
description: Returns every saved view in the calling user's org. Saved views
are shared org-wide; any user may read, create, edit, and delete any view.
operationId: ListDashboardViews
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/DashboardtypesListableDashboardView'
status:
type: string
required:
- status
- data
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: List dashboard saved views
tags:
- dashboard
post:
deprecated: false
description: Persists the calling user's dashboard listing state (query, sort,
order) as a named, reusable view shared across the org.
operationId: CreateDashboardView
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DashboardtypesPostableDashboardView'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/DashboardtypesDashboardView'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Create dashboard saved view
tags:
- dashboard
/api/v2/dashboards/views/{id}:
delete:
deprecated: false
description: Removes a saved view. Saved views are shared org-wide; any user
in the org may delete any view. Idempotent — deleting a non-existent view
returns 404.
operationId: DeleteDashboardView
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Delete dashboard saved view
tags:
- dashboard
put:
deprecated: false
description: Replaces a saved view's name and data. Saved views are shared org-wide;
any user in the org may edit any view.
operationId: UpdateDashboardView
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/DashboardtypesPostableDashboardView'
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/DashboardtypesDashboardView'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Update dashboard saved view
tags:
- dashboard
/api/v2/factor_password/forgot:
post:
deprecated: false

View File

@@ -5,12 +5,10 @@ import (
"fmt"
"log/slog"
"net/url"
"path"
"github.com/SigNoz/signoz/pkg/authn"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/http/client"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/types/authtypes"
@@ -28,14 +26,13 @@ var defaultScopes []string = []string{"email", "profile", oidc.ScopeOpenID}
var _ authn.CallbackAuthN = (*AuthN)(nil)
type AuthN struct {
settings factory.ScopedProviderSettings
store authtypes.AuthNStore
licensing licensing.Licensing
httpClient *client.Client
globalConfig global.Config
settings factory.ScopedProviderSettings
store authtypes.AuthNStore
licensing licensing.Licensing
httpClient *client.Client
}
func New(store authtypes.AuthNStore, licensing licensing.Licensing, providerSettings factory.ProviderSettings, globalConfig global.Config) (*AuthN, error) {
func New(store authtypes.AuthNStore, licensing licensing.Licensing, providerSettings factory.ProviderSettings) (*AuthN, error) {
settings := factory.NewScopedProviderSettings(providerSettings, "github.com/SigNoz/signoz/ee/authn/callbackauthn/oidccallbackauthn")
httpClient, err := client.New(providerSettings.Logger, providerSettings.TracerProvider, providerSettings.MeterProvider)
@@ -44,11 +41,10 @@ func New(store authtypes.AuthNStore, licensing licensing.Licensing, providerSett
}
return &AuthN{
settings: settings,
store: store,
licensing: licensing,
httpClient: httpClient,
globalConfig: globalConfig,
settings: settings,
store: store,
licensing: licensing,
httpClient: httpClient,
}, nil
}
@@ -201,7 +197,7 @@ func (a *AuthN) oidcProviderAndoauth2Config(ctx context.Context, siteURL *url.UR
RedirectURL: (&url.URL{
Scheme: siteURL.Scheme,
Host: siteURL.Host,
Path: path.Join(a.globalConfig.ExternalPath(), redirectPath),
Path: redirectPath,
}).String(),
}, nil
}

View File

@@ -6,12 +6,10 @@ import (
"encoding/base64"
"encoding/pem"
"net/url"
"path"
"strings"
"github.com/SigNoz/signoz/pkg/authn"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/global"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
@@ -26,16 +24,14 @@ const (
var _ authn.CallbackAuthN = (*AuthN)(nil)
type AuthN struct {
store authtypes.AuthNStore
licensing licensing.Licensing
globalConfig global.Config
store authtypes.AuthNStore
licensing licensing.Licensing
}
func New(ctx context.Context, store authtypes.AuthNStore, licensing licensing.Licensing, globalConfig global.Config) (*AuthN, error) {
func New(ctx context.Context, store authtypes.AuthNStore, licensing licensing.Licensing) (*AuthN, error) {
return &AuthN{
store: store,
licensing: licensing,
globalConfig: globalConfig,
store: store,
licensing: licensing,
}, nil
}
@@ -136,7 +132,7 @@ func (a *AuthN) serviceProvider(siteURL *url.URL, authDomain *authtypes.AuthDoma
return nil, err
}
acsURL := &url.URL{Scheme: siteURL.Scheme, Host: siteURL.Host, Path: path.Join(a.globalConfig.ExternalPath(), redirectPath)}
acsURL := &url.URL{Scheme: siteURL.Scheme, Host: siteURL.Host, Path: redirectPath}
// Note:
// The ServiceProviderIssuer is the client id in case of keycloak. Since we set it to the host here, we need to set the client id == host in keycloak.

View File

@@ -229,10 +229,47 @@ func (module *module) PatchV2(ctx context.Context, orgID valuer.UUID, id valuer.
return module.pkgDashboardModule.PatchV2(ctx, orgID, id, updatedBy, patch)
}
func (module *module) DeleteV2(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
return module.store.RunInTx(ctx, func(ctx context.Context) error {
if err := module.store.DeletePublic(ctx, id.String()); err != nil && !errors.Ast(err, errors.TypeNotFound) {
return err
}
return module.pkgDashboardModule.DeleteV2(ctx, orgID, id)
})
}
func (module *module) LockUnlockV2(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updatedBy string, isAdmin bool, lock bool) error {
return module.pkgDashboardModule.LockUnlockV2(ctx, orgID, id, updatedBy, isAdmin, lock)
}
func (module *module) ListV2(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, params *dashboardtypes.ListDashboardsV2Params) (*dashboardtypes.ListableDashboardV2, error) {
return module.pkgDashboardModule.ListV2(ctx, orgID, userID, params)
}
func (module *module) PinV2(ctx context.Context, orgID valuer.UUID, userID valuer.UUID, id valuer.UUID) error {
return module.pkgDashboardModule.PinV2(ctx, orgID, userID, id)
}
func (module *module) UnpinV2(ctx context.Context, userID valuer.UUID, id valuer.UUID) error {
return module.pkgDashboardModule.UnpinV2(ctx, userID, id)
}
func (module *module) CreateView(ctx context.Context, orgID valuer.UUID, postable dashboardtypes.PostableDashboardView) (*dashboardtypes.DashboardView, error) {
return module.pkgDashboardModule.CreateView(ctx, orgID, postable)
}
func (module *module) ListViews(ctx context.Context, orgID valuer.UUID) (*dashboardtypes.ListableDashboardView, error) {
return module.pkgDashboardModule.ListViews(ctx, orgID)
}
func (module *module) UpdateView(ctx context.Context, orgID valuer.UUID, id valuer.UUID, updateable dashboardtypes.UpdateableDashboardView) (*dashboardtypes.DashboardView, error) {
return module.pkgDashboardModule.UpdateView(ctx, orgID, id, updateable)
}
func (module *module) DeleteView(ctx context.Context, orgID valuer.UUID, id valuer.UUID) error {
return module.pkgDashboardModule.DeleteView(ctx, orgID, id)
}
func (module *module) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*dashboardtypes.Dashboard, error) {
return module.pkgDashboardModule.Get(ctx, orgID, id)
}

View File

@@ -16,10 +16,11 @@ func newFormatter(dialect schema.Dialect) sqlstore.SQLFormatter {
}
func (f *formatter) JSONExtractString(column, path string) []byte {
var sql []byte
sql = f.bunf.AppendIdent(sql, column)
sql = append(sql, f.convertJSONPathToPostgres(path)...)
return sql
ops := f.convertJSONPathToPostgres(path)
if len(ops) == 0 {
return f.bunf.AppendIdent(nil, column)
}
return append(f.TextToJsonColumn(column), ops...)
}
func (f *formatter) JSONType(column, path string) []byte {

View File

@@ -18,19 +18,19 @@ func TestJSONExtractString(t *testing.T) {
name: "simple path",
column: "data",
path: "$.field",
expected: `"data"->>'field'`,
expected: `"data"::jsonb->>'field'`,
},
{
name: "nested path",
column: "metadata",
path: "$.user.name",
expected: `"metadata"->'user'->>'name'`,
expected: `"metadata"::jsonb->'user'->>'name'`,
},
{
name: "deeply nested path",
column: "json_col",
path: "$.level1.level2.level3",
expected: `"json_col"->'level1'->'level2'->>'level3'`,
expected: `"json_col"::jsonb->'level1'->'level2'->>'level3'`,
},
{
name: "root path",

View File

@@ -0,0 +1,65 @@
package postgressqlstore
// Lives in this package (rather than the listfilter package) so it can use
// the unexported newFormatter constructor without driving a real Postgres
// connection. Covers the only listfilter cases whose emitted SQL differs
// between SQLite and Postgres — the ones that go through JSONExtractString
// (`name`, `description`). All other operators (=, !=, BETWEEN, LIKE, IN,
// EXISTS, lower(...)) emit identical ANSI SQL on both dialects and are
// covered by the SQLite tests in the listfilter package itself.
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/SigNoz/signoz/pkg/types/dashboardtypes/listfilter"
)
func TestListFilterCompile_Postgres(t *testing.T) {
f := newFormatter(pgdialect.New())
cases := []struct {
name string
query string
wantSQL string
wantArgs []any
}{
{
name: "name = uses Postgres -> / ->> chain",
query: `name = 'overview'`,
wantSQL: `"dashboard"."data"::jsonb->'spec'->'display'->>'name' = ?`,
wantArgs: []any{"overview"},
},
{
name: "name CONTAINS — same JSON path, LIKE pattern",
query: `name CONTAINS 'overview'`,
wantSQL: `"dashboard"."data"::jsonb->'spec'->'display'->>'name' LIKE ? ESCAPE '\'`,
wantArgs: []any{"%overview%"},
},
{
name: "name ILIKE — LOWER wraps the JSON path",
query: `name ILIKE 'Prod%'`,
wantSQL: `lower("dashboard"."data"::jsonb->'spec'->'display'->>'name') LIKE LOWER(?) ESCAPE '\'`,
wantArgs: []any{"Prod%"},
},
{
name: "description = follows the same path shape",
query: `description = 'd1'`,
wantSQL: `"dashboard"."data"::jsonb->'spec'->'display'->>'description' = ?`,
wantArgs: []any{"d1"},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
out, err := listfilter.Compile(c.query, f)
require.NoError(t, err)
require.NotNil(t, out)
assert.Equal(t, c.wantSQL, out.SQL)
assert.Equal(t, c.wantArgs, out.Args)
})
}
}

View File

@@ -19,13 +19,17 @@ import type {
import type {
CreateDashboardV2201,
CreateDashboardView201,
CreatePublicDashboard201,
CreatePublicDashboardPathParameters,
DashboardtypesPatchableDashboardV2DTO,
DashboardtypesPostableDashboardV2DTO,
DashboardtypesPostableDashboardViewDTO,
DashboardtypesPostablePublicDashboardDTO,
DashboardtypesUpdatableDashboardV2DTO,
DashboardtypesUpdatablePublicDashboardDTO,
DeleteDashboardV2PathParameters,
DeleteDashboardViewPathParameters,
DeletePublicDashboardPathParameters,
GetDashboardV2200,
GetDashboardV2PathParameters,
@@ -35,13 +39,20 @@ import type {
GetPublicDashboardPathParameters,
GetPublicDashboardWidgetQueryRange200,
GetPublicDashboardWidgetQueryRangePathParameters,
ListDashboardViews200,
ListDashboardsV2200,
ListDashboardsV2Params,
LockDashboardV2PathParameters,
PatchDashboardV2200,
PatchDashboardV2PathParameters,
PinDashboardV2PathParameters,
RenderErrorResponseDTO,
UnlockDashboardV2PathParameters,
UnpinDashboardV2PathParameters,
UpdateDashboardV2200,
UpdateDashboardV2PathParameters,
UpdateDashboardView200,
UpdateDashboardViewPathParameters,
UpdatePublicDashboardPathParameters,
} from '../sigNoz.schemas';
@@ -641,6 +652,103 @@ export const invalidateGetPublicDashboardWidgetQueryRange = async (
return queryClient;
};
/**
* Returns a page of v2-shape dashboards for the calling user's org. Supports a filter DSL (`query`), sort (`updated_at`/`created_at`/`name`), order (`asc`/`desc`), and offset-based pagination (`limit`/`offset`).
* @summary List dashboards (v2)
*/
export const listDashboardsV2 = (
params?: ListDashboardsV2Params,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListDashboardsV2200>({
url: `/api/v2/dashboards`,
method: 'GET',
params,
signal,
});
};
export const getListDashboardsV2QueryKey = (
params?: ListDashboardsV2Params,
) => {
return [`/api/v2/dashboards`, ...(params ? [params] : [])] as const;
};
export const getListDashboardsV2QueryOptions = <
TData = Awaited<ReturnType<typeof listDashboardsV2>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListDashboardsV2Params,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listDashboardsV2>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListDashboardsV2QueryKey(params);
const queryFn: QueryFunction<Awaited<ReturnType<typeof listDashboardsV2>>> = ({
signal,
}) => listDashboardsV2(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listDashboardsV2>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListDashboardsV2QueryResult = NonNullable<
Awaited<ReturnType<typeof listDashboardsV2>>
>;
export type ListDashboardsV2QueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List dashboards (v2)
*/
export function useListDashboardsV2<
TData = Awaited<ReturnType<typeof listDashboardsV2>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
params?: ListDashboardsV2Params,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listDashboardsV2>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListDashboardsV2QueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
return { ...query, queryKey: queryOptions.queryKey };
}
/**
* @summary List dashboards (v2)
*/
export const invalidateListDashboardsV2 = async (
queryClient: QueryClient,
params?: ListDashboardsV2Params,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListDashboardsV2QueryKey(params) },
options,
);
return queryClient;
};
/**
* This endpoint creates a dashboard in the v2 format that follows Perses spec.
* @summary Create dashboard (v2)
@@ -724,6 +832,85 @@ export const useCreateDashboardV2 = <
> => {
return useMutation(getCreateDashboardV2MutationOptions(options));
};
/**
* This endpoint deletes a v2-shape dashboard along with its tag relations. Locked dashboards are rejected.
* @summary Delete dashboard (v2)
*/
export const deleteDashboardV2 = (
{ id }: DeleteDashboardV2PathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v2/dashboards/${id}`,
method: 'DELETE',
signal,
});
};
export const getDeleteDashboardV2MutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteDashboardV2>>,
TError,
{ pathParams: DeleteDashboardV2PathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteDashboardV2>>,
TError,
{ pathParams: DeleteDashboardV2PathParameters },
TContext
> => {
const mutationKey = ['deleteDashboardV2'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteDashboardV2>>,
{ pathParams: DeleteDashboardV2PathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteDashboardV2(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteDashboardV2MutationResult = NonNullable<
Awaited<ReturnType<typeof deleteDashboardV2>>
>;
export type DeleteDashboardV2MutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete dashboard (v2)
*/
export const useDeleteDashboardV2 = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteDashboardV2>>,
TError,
{ pathParams: DeleteDashboardV2PathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteDashboardV2>>,
TError,
{ pathParams: DeleteDashboardV2PathParameters },
TContext
> => {
return useMutation(getDeleteDashboardV2MutationOptions(options));
};
/**
* This endpoint returns a v2-shape dashboard.
* @summary Get dashboard (v2)
@@ -1181,3 +1368,509 @@ export const useLockDashboardV2 = <
> => {
return useMutation(getLockDashboardV2MutationOptions(options));
};
/**
* Removes the pin for the calling user. Idempotent — unpinning a dashboard that wasn't pinned still returns 204.
* @summary Unpin a dashboard for the current user (v2)
*/
export const unpinDashboardV2 = (
{ id }: UnpinDashboardV2PathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v2/dashboards/${id}/pins/me`,
method: 'DELETE',
signal,
});
};
export const getUnpinDashboardV2MutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof unpinDashboardV2>>,
TError,
{ pathParams: UnpinDashboardV2PathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof unpinDashboardV2>>,
TError,
{ pathParams: UnpinDashboardV2PathParameters },
TContext
> => {
const mutationKey = ['unpinDashboardV2'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof unpinDashboardV2>>,
{ pathParams: UnpinDashboardV2PathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return unpinDashboardV2(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type UnpinDashboardV2MutationResult = NonNullable<
Awaited<ReturnType<typeof unpinDashboardV2>>
>;
export type UnpinDashboardV2MutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Unpin a dashboard for the current user (v2)
*/
export const useUnpinDashboardV2 = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof unpinDashboardV2>>,
TError,
{ pathParams: UnpinDashboardV2PathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof unpinDashboardV2>>,
TError,
{ pathParams: UnpinDashboardV2PathParameters },
TContext
> => {
return useMutation(getUnpinDashboardV2MutationOptions(options));
};
/**
* Pins the dashboard for the calling user. A user can pin at most 10 dashboards; pinning when at the limit returns 409. Re-pinning an already-pinned dashboard is a no-op success.
* @summary Pin a dashboard for the current user (v2)
*/
export const pinDashboardV2 = (
{ id }: PinDashboardV2PathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v2/dashboards/${id}/pins/me`,
method: 'PUT',
signal,
});
};
export const getPinDashboardV2MutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof pinDashboardV2>>,
TError,
{ pathParams: PinDashboardV2PathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof pinDashboardV2>>,
TError,
{ pathParams: PinDashboardV2PathParameters },
TContext
> => {
const mutationKey = ['pinDashboardV2'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof pinDashboardV2>>,
{ pathParams: PinDashboardV2PathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return pinDashboardV2(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type PinDashboardV2MutationResult = NonNullable<
Awaited<ReturnType<typeof pinDashboardV2>>
>;
export type PinDashboardV2MutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Pin a dashboard for the current user (v2)
*/
export const usePinDashboardV2 = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof pinDashboardV2>>,
TError,
{ pathParams: PinDashboardV2PathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof pinDashboardV2>>,
TError,
{ pathParams: PinDashboardV2PathParameters },
TContext
> => {
return useMutation(getPinDashboardV2MutationOptions(options));
};
/**
* Returns every saved view in the calling user's org. Saved views are shared org-wide; any user may read, create, edit, and delete any view.
* @summary List dashboard saved views
*/
export const listDashboardViews = (signal?: AbortSignal) => {
return GeneratedAPIInstance<ListDashboardViews200>({
url: `/api/v2/dashboards/views`,
method: 'GET',
signal,
});
};
export const getListDashboardViewsQueryKey = () => {
return [`/api/v2/dashboards/views`] as const;
};
export const getListDashboardViewsQueryOptions = <
TData = Awaited<ReturnType<typeof listDashboardViews>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listDashboardViews>>,
TError,
TData
>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListDashboardViewsQueryKey();
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listDashboardViews>>
> = ({ signal }) => listDashboardViews(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listDashboardViews>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListDashboardViewsQueryResult = NonNullable<
Awaited<ReturnType<typeof listDashboardViews>>
>;
export type ListDashboardViewsQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List dashboard saved views
*/
export function useListDashboardViews<
TData = Awaited<ReturnType<typeof listDashboardViews>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listDashboardViews>>,
TError,
TData
>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListDashboardViewsQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
return { ...query, queryKey: queryOptions.queryKey };
}
/**
* @summary List dashboard saved views
*/
export const invalidateListDashboardViews = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListDashboardViewsQueryKey() },
options,
);
return queryClient;
};
/**
* Persists the calling user's dashboard listing state (query, sort, order) as a named, reusable view shared across the org.
* @summary Create dashboard saved view
*/
export const createDashboardView = (
dashboardtypesPostableDashboardViewDTO?: BodyType<DashboardtypesPostableDashboardViewDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateDashboardView201>({
url: `/api/v2/dashboards/views`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: dashboardtypesPostableDashboardViewDTO,
signal,
});
};
export const getCreateDashboardViewMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDashboardView>>,
TError,
{ data?: BodyType<DashboardtypesPostableDashboardViewDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createDashboardView>>,
TError,
{ data?: BodyType<DashboardtypesPostableDashboardViewDTO> },
TContext
> => {
const mutationKey = ['createDashboardView'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createDashboardView>>,
{ data?: BodyType<DashboardtypesPostableDashboardViewDTO> }
> = (props) => {
const { data } = props ?? {};
return createDashboardView(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateDashboardViewMutationResult = NonNullable<
Awaited<ReturnType<typeof createDashboardView>>
>;
export type CreateDashboardViewMutationBody =
| BodyType<DashboardtypesPostableDashboardViewDTO>
| undefined;
export type CreateDashboardViewMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create dashboard saved view
*/
export const useCreateDashboardView = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDashboardView>>,
TError,
{ data?: BodyType<DashboardtypesPostableDashboardViewDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createDashboardView>>,
TError,
{ data?: BodyType<DashboardtypesPostableDashboardViewDTO> },
TContext
> => {
return useMutation(getCreateDashboardViewMutationOptions(options));
};
/**
* Removes a saved view. Saved views are shared org-wide; any user in the org may delete any view. Idempotent — deleting a non-existent view returns 404.
* @summary Delete dashboard saved view
*/
export const deleteDashboardView = (
{ id }: DeleteDashboardViewPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v2/dashboards/views/${id}`,
method: 'DELETE',
signal,
});
};
export const getDeleteDashboardViewMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteDashboardView>>,
TError,
{ pathParams: DeleteDashboardViewPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteDashboardView>>,
TError,
{ pathParams: DeleteDashboardViewPathParameters },
TContext
> => {
const mutationKey = ['deleteDashboardView'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteDashboardView>>,
{ pathParams: DeleteDashboardViewPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteDashboardView(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteDashboardViewMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteDashboardView>>
>;
export type DeleteDashboardViewMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete dashboard saved view
*/
export const useDeleteDashboardView = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteDashboardView>>,
TError,
{ pathParams: DeleteDashboardViewPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteDashboardView>>,
TError,
{ pathParams: DeleteDashboardViewPathParameters },
TContext
> => {
return useMutation(getDeleteDashboardViewMutationOptions(options));
};
/**
* Replaces a saved view's name and data. Saved views are shared org-wide; any user in the org may edit any view.
* @summary Update dashboard saved view
*/
export const updateDashboardView = (
{ id }: UpdateDashboardViewPathParameters,
dashboardtypesPostableDashboardViewDTO?: BodyType<DashboardtypesPostableDashboardViewDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<UpdateDashboardView200>({
url: `/api/v2/dashboards/views/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: dashboardtypesPostableDashboardViewDTO,
signal,
});
};
export const getUpdateDashboardViewMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateDashboardView>>,
TError,
{
pathParams: UpdateDashboardViewPathParameters;
data?: BodyType<DashboardtypesPostableDashboardViewDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateDashboardView>>,
TError,
{
pathParams: UpdateDashboardViewPathParameters;
data?: BodyType<DashboardtypesPostableDashboardViewDTO>;
},
TContext
> => {
const mutationKey = ['updateDashboardView'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateDashboardView>>,
{
pathParams: UpdateDashboardViewPathParameters;
data?: BodyType<DashboardtypesPostableDashboardViewDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateDashboardView(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateDashboardViewMutationResult = NonNullable<
Awaited<ReturnType<typeof updateDashboardView>>
>;
export type UpdateDashboardViewMutationBody =
| BodyType<DashboardtypesPostableDashboardViewDTO>
| undefined;
export type UpdateDashboardViewMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update dashboard saved view
*/
export const useUpdateDashboardView = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateDashboardView>>,
TError,
{
pathParams: UpdateDashboardViewPathParameters;
data?: BodyType<DashboardtypesPostableDashboardViewDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateDashboardView>>,
TError,
{
pathParams: UpdateDashboardViewPathParameters;
data?: BodyType<DashboardtypesPostableDashboardViewDTO>;
},
TContext
> => {
return useMutation(getUpdateDashboardViewMutationOptions(options));
};

View File

@@ -4616,10 +4616,58 @@ export interface DashboardtypesDashboardSpecDTO {
variables?: DashboardtypesVariableDTO[];
}
export enum DashboardtypesListOrderDTO {
asc = 'asc',
desc = 'desc',
}
export enum DashboardtypesListSortDTO {
updated_at = 'updated_at',
created_at = 'created_at',
name = 'name',
}
export interface DashboardtypesDashboardViewDataDTO {
order?: DashboardtypesListOrderDTO;
/**
* @type string
*/
query?: string;
sort?: DashboardtypesListSortDTO;
/**
* @type string
*/
version: string;
}
export interface DashboardtypesDashboardViewDTO {
/**
* @type string
* @format date-time
*/
createdAt?: string;
data: DashboardtypesDashboardViewDataDTO;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
*/
orgId: string;
/**
* @type string
* @format date-time
*/
updatedAt?: string;
}
export enum DashboardtypesDatasourcePluginKindDTO {
'signoz/Datasource' = 'signoz/Datasource',
}
export interface TagtypesPostableTagDTO {
export interface TagtypesGettableTagDTO {
/**
* @type string
*/
@@ -4669,7 +4717,7 @@ export interface DashboardtypesGettableDashboardV2DTO {
/**
* @type array,null
*/
tags: TagtypesPostableTagDTO[] | null;
tags: TagtypesGettableTagDTO[] | null;
/**
* @type string
* @format date-time
@@ -4727,6 +4775,88 @@ export interface DashboardtypesJSONPatchOperationDTO {
value?: unknown;
}
export interface DashboardtypesListedDashboardV2SpecDTO {
display?: CommonDisplayDTO;
}
export interface DashboardtypesListedDashboardV2DTO {
/**
* @type string
* @format date-time
*/
createdAt?: string;
/**
* @type string
*/
createdBy?: string;
/**
* @type string
*/
id: string;
/**
* @type string
*/
image?: string;
/**
* @type boolean
*/
locked: boolean;
/**
* @type string
*/
name: string;
/**
* @type string
*/
orgId: string;
/**
* @type boolean
*/
pinned: boolean;
/**
* @type string
*/
schemaVersion: string;
source: DashboardtypesSourceDTO;
spec: DashboardtypesListedDashboardV2SpecDTO;
/**
* @type array
*/
tags: TagtypesGettableTagDTO[];
/**
* @type string
* @format date-time
*/
updatedAt?: string;
/**
* @type string
*/
updatedBy?: string;
}
export interface DashboardtypesListableDashboardV2DTO {
/**
* @type array
*/
dashboards: DashboardtypesListedDashboardV2DTO[];
/**
* @type array
*/
tags: TagtypesGettableTagDTO[];
/**
* @type integer
* @format int64
*/
total: number;
}
export interface DashboardtypesListableDashboardViewDTO {
/**
* @type array
*/
views: DashboardtypesDashboardViewDTO[];
}
export enum DashboardtypesPanelPluginKindDTO {
'signoz/TimeSeriesPanel' = 'signoz/TimeSeriesPanel',
'signoz/BarChartPanel' = 'signoz/BarChartPanel',
@@ -4743,6 +4873,17 @@ export type DashboardtypesPatchableDashboardV2DTO =
| DashboardtypesJSONPatchOperationDTO[]
| null;
export interface TagtypesPostableTagDTO {
/**
* @type string
*/
key: string;
/**
* @type string
*/
value: string;
}
export interface DashboardtypesPostableDashboardV2DTO {
/**
* @type boolean
@@ -4767,6 +4908,14 @@ export interface DashboardtypesPostableDashboardV2DTO {
tags: TagtypesPostableTagDTO[] | null;
}
export interface DashboardtypesPostableDashboardViewDTO {
data: DashboardtypesDashboardViewDataDTO;
/**
* @type string
*/
name: string;
}
export interface DashboardtypesPostablePublicDashboardDTO {
/**
* @type string
@@ -9576,6 +9725,40 @@ export type GetUserPreference200 = {
export type UpdateUserPreferencePathParameters = {
name: string;
};
export type ListDashboardsV2Params = {
/**
* @type string
* @description undefined
*/
query?: string;
/**
* @description undefined
*/
sort?: DashboardtypesListSortDTO;
/**
* @description undefined
*/
order?: DashboardtypesListOrderDTO;
/**
* @type integer
* @description undefined
*/
limit?: number;
/**
* @type integer
* @description undefined
*/
offset?: number;
};
export type ListDashboardsV2200 = {
data: DashboardtypesListableDashboardV2DTO;
/**
* @type string
*/
status: string;
};
export type CreateDashboardV2201 = {
data: DashboardtypesGettableDashboardV2DTO;
/**
@@ -9584,6 +9767,9 @@ export type CreateDashboardV2201 = {
status: string;
};
export type DeleteDashboardV2PathParameters = {
id: string;
};
export type GetDashboardV2PathParameters = {
id: string;
};
@@ -9623,6 +9809,42 @@ export type UnlockDashboardV2PathParameters = {
export type LockDashboardV2PathParameters = {
id: string;
};
export type UnpinDashboardV2PathParameters = {
id: string;
};
export type PinDashboardV2PathParameters = {
id: string;
};
export type ListDashboardViews200 = {
data: DashboardtypesListableDashboardViewDTO;
/**
* @type string
*/
status: string;
};
export type CreateDashboardView201 = {
data: DashboardtypesDashboardViewDTO;
/**
* @type string
*/
status: string;
};
export type DeleteDashboardViewPathParameters = {
id: string;
};
export type UpdateDashboardViewPathParameters = {
id: string;
};
export type UpdateDashboardView200 = {
data: DashboardtypesDashboardViewDTO;
/**
* @type string
*/
status: string;
};
export type GetFeatures200 = {
/**
* @type array

View File

@@ -1,5 +1,5 @@
.alertHistory {
.alert-history {
display: flex;
flex-direction: column;
gap: var(--spacing-10);
gap: 24px;
}

View File

@@ -3,13 +3,13 @@ import { useState } from 'react';
import Statistics from './Statistics/Statistics';
import Timeline from './Timeline/Timeline';
import styles from './AlertHistory.module.scss';
import './AlertHistory.styles.scss';
function AlertHistory(): JSX.Element {
const [totalCurrentTriggers, setTotalCurrentTriggers] = useState(0);
return (
<div className={styles.alertHistory}>
<div className="alert-history">
<Statistics
totalCurrentTriggers={totalCurrentTriggers}
setTotalCurrentTriggers={setTotalCurrentTriggers}

View File

@@ -1,40 +0,0 @@
.alertPopoverTriggerAction {
cursor: pointer;
}
.alertHistoryPopover {
:global(.ant-popover-inner) {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
padding: 0 !important;
}
:global(.ant-popover-arrow) {
&::before {
background: var(--l1-background);
}
}
}
.contributorRowPopoverButtons {
display: flex;
flex-direction: column;
}
.contributorRowPopoverButtonsButton {
display: flex;
align-items: center;
gap: var(--spacing-3);
padding: var(--spacing-6) var(--spacing-7);
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
letter-spacing: 0.14px;
width: 160px;
cursor: pointer;
background: var(--l1-background);
border-color: var(--l1-border);
&:hover {
background: var(--l2-background);
}
}

View File

@@ -0,0 +1,15 @@
.alert-popover-trigger-action {
cursor: pointer;
}
.alert-history-popover {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
}
.ant-popover-arrow {
&::before {
background: var(--l1-background);
}
}
}

View File

@@ -7,7 +7,7 @@ import ROUTES from 'constants/routes';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { DraftingCompass } from '@signozhq/icons';
import styles from './AlertPopover.module.scss';
import './AlertPopover.styles.scss';
type Props = {
children: React.ReactNode;
@@ -24,30 +24,30 @@ function PopoverContent({
}): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
<div className={styles.contributorRowPopoverButtons}>
<div className="contributor-row-popover-buttons">
{!!relatedLogsLink && (
<Link
to={`${ROUTES.LOGS_EXPLORER}?${relatedLogsLink}`}
className={styles.contributorRowPopoverButtonsButton}
className="contributor-row-popover-buttons__button"
>
<div>
<div className="icon">
<LogsIcon />
</div>
<div>View Logs</div>
<div className="text">View Logs</div>
</Link>
)}
{!!relatedTracesLink && (
<Link
to={`${ROUTES.TRACES_EXPLORER}?${relatedTracesLink}`}
className={styles.contributorRowPopoverButtonsButton}
className="contributor-row-popover-buttons__button"
>
<div>
<div className="icon">
<DraftingCompass
size={14}
color={isDarkMode ? Color.BG_VANILLA_400 : Color.TEXT_INK_400}
/>
</div>
<div>View Traces</div>
<div className="text">View Traces</div>
</Link>
)}
</div>
@@ -64,13 +64,13 @@ function AlertPopover({
relatedLogsLink,
}: Props): JSX.Element {
return (
<div className={styles.alertPopoverTriggerAction}>
<div className="alert-popover-trigger-action">
<Popover
showArrow={false}
placement="bottom"
color="linear-gradient(139deg, rgba(18, 19, 23, 1) 0%, rgba(18, 19, 23, 1) 98.68%)"
destroyTooltipOnHide
rootClassName={styles.alertHistoryPopover}
rootClassName="alert-history-popover"
content={
<PopoverContent
relatedTracesLink={relatedTracesLink}
@@ -112,3 +112,4 @@ export function ConditionalAlertPopover({
}
return <div>{children}</div>;
}
export default AlertPopover;

View File

@@ -4,5 +4,5 @@
height: 280px;
border: 1px solid var(--l1-border);
border-radius: 4px;
margin: 0 var(--spacing-8);
margin: 0 16px;
}

View File

@@ -3,7 +3,7 @@ import { AlertRuleStats } from 'types/api/alerts/def';
import StatsCardsRenderer from './StatsCardsRenderer/StatsCardsRenderer';
import TopContributorsRenderer from './TopContributorsRenderer/TopContributorsRenderer';
import styles from './Statistics.module.scss';
import './Statistics.styles.scss';
function Statistics({
setTotalCurrentTriggers,
@@ -13,7 +13,7 @@ function Statistics({
totalCurrentTriggers: AlertRuleStats['totalCurrentTriggers'];
}): JSX.Element {
return (
<div className={styles.statistics}>
<div className="statistics">
<StatsCardsRenderer setTotalCurrentTriggers={setTotalCurrentTriggers} />
<TopContributorsRenderer totalCurrentTriggers={totalCurrentTriggers} />
</div>

View File

@@ -1,102 +0,0 @@
.statsCard {
width: 21.7%;
border-right: 1px solid var(--l1-border);
padding: var(--spacing-4) var(--spacing-6) var(--spacing-6);
}
.statsCardEmpty {
justify-content: normal;
}
.statsCardTitleWrapper {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
text-transform: uppercase;
font-size: var(--periscope-font-size-base);
line-height: 22px;
color: var(--l2-foreground);
font-weight: var(--font-weight-medium);
}
.durationIndicator {
display: flex;
align-items: center;
gap: var(--spacing-2);
}
.icon {
display: flex;
align-self: center;
}
.text {
text-transform: uppercase;
color: var(--l3-foreground);
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-semibold);
letter-spacing: 0.48px;
}
.statsCardStats {
margin-top: var(--spacing-10);
display: flex;
flex-direction: column;
gap: var(--spacing-2);
}
.countLabel {
color: var(--l1-foreground);
font-family: var(--periscope-font-family-mono);
font-size: var(--font-size-2xl);
line-height: 36px;
}
.statsCardAlertHistoryGraph {
margin-top: var(--spacing-16);
}
.alertHistoryGraph {
width: 100%;
height: 72px;
}
.changePercentage {
width: max-content;
display: flex;
padding: var(--spacing-2) var(--spacing-4);
border-radius: 20px;
align-items: center;
gap: var(--spacing-2);
}
// TODO(@signozhq/design-tokens): replace --text-forest-* with --success-foreground after release
.changePercentageSuccess {
background: color-mix(in srgb, var(--text-forest-500) 10%, transparent);
color: var(--text-forest-400);
}
.changePercentageError {
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
color: var(--danger-foreground);
}
.changePercentageNoPreviousData {
color: var(--primary-foreground);
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
padding: var(--spacing-2) var(--spacing-8);
}
.changePercentageIcon {
display: flex;
align-self: center;
}
.changePercentageLabel {
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-medium);
line-height: 16px;
}

View File

@@ -0,0 +1,94 @@
.stats-card {
width: 21.7%;
border-right: 1px solid var(--l1-border);
padding: 9px 12px 13px;
&--empty {
justify-content: normal;
}
&__title-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
.title {
text-transform: uppercase;
font-size: 13px;
line-height: 22px;
color: var(--l2-foreground);
font-weight: 500;
}
.duration-indicator {
display: flex;
align-items: center;
gap: 4px;
.icon {
display: flex;
align-self: center;
}
.text {
text-transform: uppercase;
color: var(--l3-foreground);
font-size: 12px;
font-weight: 600;
letter-spacing: 0.48px;
}
}
}
&__stats {
margin-top: 20px;
display: flex;
flex-direction: column;
gap: 4px;
.count-label {
color: var(--l1-foreground);
font-family: 'Geist Mono';
font-size: 24px;
line-height: 36px;
}
}
&__alert-history-graph {
margin-top: 80px;
.alert-history-graph {
width: 100%;
height: 72px;
}
}
}
.change-percentage {
width: max-content;
display: flex;
padding: 4px 8px;
border-radius: 20px;
align-items: center;
gap: 4px;
&--success {
background: color-mix(in srgb, var(--text-forest-500) 10%, transparent);
color: var(--text-forest-400);
}
&--error {
background: color-mix(in srgb, var(--error-background) 10%, transparent);
color: var(--error-foreground);
}
&--no-previous-data {
color: var(--primary-foreground);
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
padding: 4px 16px;
}
&__icon {
display: flex;
align-self: center;
}
&__label {
font-size: 12px;
font-weight: 500;
line-height: 16px;
}
}

View File

@@ -1,4 +1,3 @@
import cx from 'classnames';
import { Color } from '@signozhq/design-tokens';
import { Tooltip } from 'antd';
import { QueryParams } from 'constants/query';
@@ -13,7 +12,7 @@ import {
extractDayFromTimestamp,
} from './utils';
import styles from './StatsCard.module.scss';
import './StatsCard.styles.scss';
type ChangePercentageProps = {
percentage: number;
@@ -27,11 +26,11 @@ function ChangePercentage({
}: ChangePercentageProps): JSX.Element {
if (direction > 0) {
return (
<div className={cx(styles.changePercentage, styles.changePercentageSuccess)}>
<div className={styles.changePercentageIcon}>
<div className="change-percentage change-percentage--success">
<div className="change-percentage__icon">
<ArrowDownLeft size={14} color={Color.BG_FOREST_500} />
</div>
<div className={styles.changePercentageLabel}>
<div className="change-percentage__label">
{percentage}% vs Last {duration}
</div>
</div>
@@ -39,11 +38,11 @@ function ChangePercentage({
}
if (direction < 0) {
return (
<div className={cx(styles.changePercentage, styles.changePercentageError)}>
<div className={styles.changePercentageIcon}>
<div className="change-percentage change-percentage--error">
<div className="change-percentage__icon">
<ArrowUpRight size={14} color={Color.BG_CHERRY_500} />
</div>
<div className={styles.changePercentageLabel}>
<div className="change-percentage__label">
{percentage}% vs Last {duration}
</div>
</div>
@@ -51,13 +50,8 @@ function ChangePercentage({
}
return (
<div
className={cx(
styles.changePercentage,
styles.changePercentageNoPreviousData,
)}
>
<div className={styles.changePercentageLabel}>no previous data</div>
<div className="change-percentage change-percentage--no-previous-data">
<div className="change-percentage__label">no previous data</div>
</div>
);
}
@@ -109,27 +103,27 @@ function StatsCard({
const formattedEndTimeForTooltip = convertTimestampToLocaleDateString(endTime);
return (
<div className={cx(styles.statsCard, { [styles.statsCardEmpty]: isEmpty })}>
<div className={styles.statsCardTitleWrapper}>
<div className={styles.title}>{title}</div>
<div className={styles.durationIndicator}>
<div className={styles.icon}>
<div className={`stats-card ${isEmpty ? 'stats-card--empty' : ''}`}>
<div className="stats-card__title-wrapper">
<div className="title">{title}</div>
<div className="duration-indicator">
<div className="icon">
<Calendar size={14} color={Color.BG_SLATE_200} />
</div>
{relativeTime ? (
<div className={styles.text}>{displayTime}</div>
<div className="text">{displayTime}</div>
) : (
<Tooltip
title={`From ${formattedStartTimeForTooltip} to ${formattedEndTimeForTooltip}`}
>
<div className={styles.text}>{displayTime}</div>
<div className="text">{displayTime}</div>
</Tooltip>
)}
</div>
</div>
<div className={styles.statsCardStats}>
<div className={styles.countLabel}>
<div className="stats-card__stats">
<div className="count-label">
{isEmpty ? emptyMessage : displayValue || totalCurrentCount}
</div>
@@ -140,8 +134,8 @@ function StatsCard({
/>
</div>
<div className={styles.statsCardAlertHistoryGraph}>
<div className={styles.alertHistoryGraph}>
<div className="stats-card__alert-history-graph">
<div className="alert-history-graph">
{!isEmpty && timeSeries.length > 1 && (
<StatsGraph timeSeries={timeSeries} changeDirection={changeDirection} />
)}

View File

@@ -1,45 +0,0 @@
.topContributorsCard {
width: 56.6%;
overflow: hidden;
}
.topContributorsCardHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-4) var(--spacing-6);
border-bottom: 1px solid var(--l1-border);
}
.title {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
line-height: 22px;
letter-spacing: 0.52px;
text-transform: uppercase;
}
.viewAll {
display: flex;
align-items: center;
gap: var(--spacing-2);
cursor: pointer;
padding: 0;
height: var(--line-height-20);
&:hover {
background-color: transparent !important;
}
}
.label {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: var(--line-height-20);
letter-spacing: -0.07px;
}
.icon {
display: flex;
}

View File

@@ -0,0 +1,163 @@
.top-contributors-card {
width: 56.6%;
overflow: hidden;
&--view-all {
width: auto;
}
&__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 12px;
border-bottom: 1px solid var(--l1-border);
.title {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 500;
line-height: 22px;
letter-spacing: 0.52px;
text-transform: uppercase;
}
.view-all {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
padding: 0;
height: 20px;
&:hover {
background-color: transparent !important;
}
.label {
color: var(--l2-foreground);
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
}
.icon {
display: flex;
}
}
}
.contributors-row {
height: 80px;
}
.top-contributors-progress {
--progress-background: transparent;
}
&__content {
.ant-table {
&-cell {
padding: 12px !important;
}
}
.contributors-row {
background: var(--l1-background);
td {
border: none !important;
}
&:not(:last-of-type) td {
border-bottom: 1px solid var(--l1-border) !important;
}
}
.total-contribution {
color: var(--primary-foreground);
font-family: 'Geist Mono';
font-size: 12px;
font-weight: 500;
letter-spacing: -0.06px;
padding: 4px 8px;
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
border-radius: 50px;
width: max-content;
}
}
.empty-content {
margin: 16px 12px;
padding: 40px 45px;
display: flex;
flex-direction: column;
gap: 12px;
border: 1px dashed var(--l1-border);
border-radius: 6px;
&__icon {
font-family: Inter;
font-size: 20px;
line-height: 26px;
letter-spacing: -0.103px;
}
&__text {
color: var(--l2-foreground);
line-height: 18px;
.bold-text {
color: var(--l1-foreground);
font-weight: 500;
}
}
&__button-wrapper {
margin-top: 12px;
.configure-alert-rule-button {
padding: 8px 16px;
border-radius: 2px;
background: var(--l3-background);
border-width: 0;
color: var(--l1-foreground);
line-height: 24px;
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
}
}
}
}
.ant-popover-inner:has(.contributor-row-popover-buttons) {
padding: 0 !important;
}
.contributor-row-popover-buttons {
display: flex;
flex-direction: column;
&__button {
display: flex;
align-items: center;
gap: 6px;
padding: 12px 15px;
color: var(--l2-foreground);
font-size: 14px;
letter-spacing: 0.14px;
width: 160px;
cursor: pointer;
background: var(--l1-background);
border-color: var(--l1-border);
.text,
.icon {
color: var(--l1-foreground);
}
&:hover {
background: var(--l2-background);
.text,
.icon {
color: var(--l1-foreground);
}
}
.icon {
display: flex;
}
}
}
.view-all-drawer {
border-radius: 4px;
}

View File

@@ -10,7 +10,7 @@ import TopContributorsContent from './TopContributorsContent';
import { TopContributorsCardProps } from './types';
import ViewAllDrawer from './ViewAllDrawer';
import styles from './TopContributorsCard.module.scss';
import './TopContributorsCard.styles.scss';
function TopContributorsCard({
topContributorsData,
@@ -48,17 +48,13 @@ function TopContributorsCard({
return (
<>
<div className={styles.topContributorsCard}>
<div className={styles.topContributorsCardHeader}>
<div className={styles.title}>top contributors</div>
<div className="top-contributors-card">
<div className="top-contributors-card__header">
<div className="title">top contributors</div>
{topContributorsData.length > 3 && (
<Button
type="text"
className={styles.viewAll}
onClick={toggleViewAllDrawer}
>
<div className={styles.label}>View all</div>
<div className={styles.icon}>
<Button type="text" className="view-all" onClick={toggleViewAllDrawer}>
<div className="label">View all</div>
<div className="icon">
<ArrowRight
size={14}
color={isDarkMode ? Color.BG_VANILLA_400 : Color.BG_INK_400}

View File

@@ -1,27 +0,0 @@
.emptyContent {
margin: var(--spacing-8) var(--spacing-6);
padding: var(--spacing-20) 45px;
display: flex;
flex-direction: column;
gap: var(--spacing-6);
border: 1px dashed var(--l1-border);
border-radius: 6px;
}
.emptyContentIcon {
font-family: var(--font-family-inter);
font-size: var(--font-size-xl);
line-height: 26px;
letter-spacing: -0.103px;
}
.emptyContentText {
color: var(--l2-foreground);
line-height: var(--line-height-18);
}
.topContributorsCardContent {
:global(.ant-table-cell) {
padding: var(--spacing-6) !important;
}
}

View File

@@ -1,4 +1,3 @@
import styles from './TopContributorsContent.module.scss';
import TopContributorsRows from './TopContributorsRows';
import { TopContributorsCardProps } from './types';
@@ -10,9 +9,9 @@ function TopContributorsContent({
if (isEmpty) {
return (
<div className={styles.emptyContent}>
<div className={styles.emptyContentIcon}></div>
<div className={styles.emptyContentText}>
<div className="empty-content">
<div className="empty-content__icon"></div>
<div className="empty-content__text">
Top contributors highlight the most frequently triggering group-by
attributes in multi-dimensional alerts
</div>
@@ -21,7 +20,7 @@ function TopContributorsContent({
}
return (
<div className={styles.topContributorsCardContent}>
<div className="top-contributors-card__content">
<TopContributorsRows
topContributors={topContributorsData.slice(0, 3)}
totalCurrentTriggers={totalCurrentTriggers}

View File

@@ -1,28 +0,0 @@
.contributorsRow {
height: 80px;
background: var(--l1-background);
td {
border: none !important;
}
&:not(:last-of-type) td {
border-bottom: 1px solid var(--l1-border) !important;
}
}
.topContributorsProgress {
--progress-background: transparent;
}
.totalContribution {
color: var(--primary-foreground);
font-family: var(--periscope-font-family-mono);
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-medium);
letter-spacing: -0.06px;
padding: var(--spacing-2) var(--spacing-4);
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
border-radius: 50px;
width: max-content;
}

View File

@@ -12,8 +12,6 @@ import {
AlertRuleTopContributors,
} from 'types/api/alerts/def';
import styles from './TopContributorsRows.module.scss';
function TopContributorsRows({
topContributors,
totalCurrentTriggers,
@@ -55,7 +53,7 @@ function TopContributorsRows({
percent={(count / totalCurrentTriggers) * 100}
showInfo={false}
strokeColor={Color.BG_ROBIN_500}
className={styles.topContributorsProgress}
className="top-contributors-progress"
/>
</ConditionalAlertPopover>
),
@@ -70,7 +68,7 @@ function TopContributorsRows({
relatedTracesLink={record.relatedTracesLink}
relatedLogsLink={record.relatedLogsLink}
>
<div className={styles.totalContribution}>
<div className="total-contribution">
{count}/{totalCurrentTriggers}
</div>
</ConditionalAlertPopover>
@@ -90,7 +88,7 @@ function TopContributorsRows({
return (
<Table
rowClassName={styles.contributorsRow}
rowClassName="contributors-row"
rowKey={(row): string => `top-contributor-${row.fingerprint}`}
onRow={handleRowClick}
columns={columns}

View File

@@ -1,13 +0,0 @@
.topContributorsCardViewAll {
width: auto;
}
.topContributorsCardContent {
:global(.ant-table-cell) {
padding: var(--spacing-6) !important;
}
}
.viewAllDrawer {
border-radius: 4px;
}

View File

@@ -3,7 +3,6 @@ import { Drawer } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { AlertRuleStats, AlertRuleTopContributors } from 'types/api/alerts/def';
import styles from './ViewAllDrawer.module.scss';
import TopContributorsRows from './TopContributorsRows';
function ViewAllDrawer({
@@ -25,15 +24,15 @@ function ViewAllDrawer({
onClose={toggleViewAllDrawer}
placement="right"
width="50%"
className={styles.viewAllDrawer}
className="view-all-drawer"
style={{
overscrollBehavior: 'contain',
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
}}
title="Viewing All Contributors"
>
<div className={styles.topContributorsCardViewAll}>
<div className={styles.topContributorsCardContent}>
<div className="top-contributors-card--view-all">
<div className="top-contributors-card__content">
<TopContributorsRows
topContributors={topContributorsData}
totalCurrentTriggers={totalCurrentTriggers}

View File

@@ -0,0 +1,35 @@
.timeline-graph {
display: flex;
flex-direction: column;
gap: 24px;
background: var(--l2-background);
padding: 12px;
border-radius: 4px;
border: 1px solid var(--l1-border);
height: 150px;
&__title {
width: max-content;
padding: 2px 8px;
border-radius: 4px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
font-size: 12px;
line-height: 18px;
letter-spacing: -0.06px;
}
&__chart {
.chart-placeholder {
width: 100%;
height: 52px;
background: color-mix(in srgb, var(--l1-foreground) 12%, transparent);
display: flex;
align-items: center;
justify-content: center;
.chart-icon {
font-size: 2rem;
}
}
}
}

View File

@@ -1,22 +0,0 @@
.timelineGraph {
display: flex;
flex-direction: column;
gap: var(--spacing-10);
background: var(--l2-background);
padding: var(--spacing-6);
border-radius: 4px;
border: 1px solid var(--l1-border);
height: 150px;
}
.timelineGraphTitle {
width: max-content;
padding: var(--spacing-1) var(--spacing-4);
border-radius: 4px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
font-size: var(--periscope-font-size-small);
line-height: var(--line-height-18);
letter-spacing: -0.06px;
}

View File

@@ -4,7 +4,7 @@ import DataStateRenderer from 'periscope/components/DataStateRenderer/DataStateR
import Graph from '../Graph/Graph';
import styles from './GraphWrapper.module.scss';
import '../Graph/Graph.styles.scss';
function GraphWrapper({
totalCurrentTriggers,
@@ -40,11 +40,11 @@ function GraphWrapper({
// }, [startTime]);
return (
<div className={styles.timelineGraph}>
<div className={styles.timelineGraphTitle}>
<div className="timeline-graph">
<div className="timeline-graph__title">
{totalCurrentTriggers} triggers in {relativeTime}
</div>
<div>
<div className="timeline-graph__chart">
<DataStateRenderer
isLoading={isLoading}
isError={isError || !isValidRuleId || !ruleId}

View File

@@ -1,70 +0,0 @@
.timelineTable {
border-top: 1px solid var(--l1-border);
border-radius: 6px;
overflow: hidden;
margin-top: var(--spacing-2);
min-height: 600px;
:global(.ant-table) {
background: var(--l1-background);
}
:global(.ant-table-cell) {
padding: var(--spacing-6) var(--spacing-8) !important;
vertical-align: baseline;
&::before {
display: none;
}
}
:global(.ant-table-thead) > tr > th {
border-color: var(--l1-border);
background: var(--l1-background);
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-medium);
padding: var(--spacing-6) var(--spacing-8) var(--spacing-4) !important;
}
:global(.ant-table-tbody) > tr > td {
border: none;
}
:global(.ant-table.ant-table-middle) {
border-bottom: 1px solid var(--l1-border);
border-left: 1px solid var(--l1-border);
border-right: 1px solid var(--l1-border);
border-radius: 6px;
}
:global(.ant-pagination-item-active) {
display: flex;
width: var(--spacing-10);
height: var(--spacing-10);
align-items: center;
justify-content: center;
padding: var(--spacing-0) var(--spacing-4);
border-radius: 2px;
background: var(--primary-background);
& > a {
color: var(--primary-foreground);
line-height: var(--line-height-20);
font-weight: var(--font-weight-medium);
}
}
}
.alertRuleCreatedAt {
font-size: var(--periscope-font-size-base);
color: var(--l2-foreground);
line-height: var(--line-height-18);
letter-spacing: -0.07px;
}
.alertHistoryLabelSearch {
:global(.ant-select-selector) {
border: none;
background: var(--l2-background);
}
}

View File

@@ -0,0 +1,89 @@
.timeline-table {
border-top: 1px solid var(--l1-border);
border-radius: 6px;
overflow: hidden;
margin-top: 4px;
min-height: 600px;
.ant-table {
background: var(--l1-background);
&-cell {
padding: 12px 16px !important;
vertical-align: baseline;
&::before {
display: none;
}
}
&-thead > tr > th {
border-color: var(--l1-border);
background: var(--l1-background);
font-size: 12px;
font-weight: 500;
padding: 12px 16px 8px !important;
}
&-tbody > tr > td {
border: none;
}
}
.label-filter {
padding: 6px 8px;
border-radius: 4px;
background: var(--l1-foreground);
border-width: 0;
line-height: 18px;
& ::placeholder {
color: var(--l2-foreground);
font-size: 12px;
letter-spacing: 0.6px;
text-transform: uppercase;
font-weight: 500;
}
}
.alert-rule {
&-value,
&__created-at {
font-size: 14px;
color: var(--l2-foreground);
}
&-value {
font-weight: 500;
line-height: 20px;
}
&__created-at {
line-height: 18px;
letter-spacing: -0.07px;
}
}
.ant-table.ant-table-middle {
border-bottom: 1px solid var(--l1-border);
border-left: 1px solid var(--l1-border);
border-right: 1px solid var(--l1-border);
border-radius: 6px;
}
.ant-pagination-item {
&-active {
display: flex;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
padding: 1px 8px;
border-radius: 2px;
background: var(--primary-background);
& > a {
color: var(--primary-foreground);
line-height: 20px;
font-weight: 500;
}
}
}
.alert-history-label-search {
.ant-select-selector {
border: none;
background: var(--l2-background);
}
}
}

View File

@@ -13,7 +13,7 @@ import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { timelineTableColumns } from './useTimelineTable';
import styles from './Table.module.scss';
import './Table.styles.scss';
function TimelineTable(): JSX.Element {
const [filters, setFilters] = useState<TagFilter>(initialFilters);
@@ -54,7 +54,7 @@ function TimelineTable(): JSX.Element {
});
return (
<div className={styles.timelineTable}>
<div className="timeline-table">
<Table
rowKey={(row): string => `${row.fingerprint}-${row.value}-${row.unixMilli}`}
columns={timelineTableColumns({

View File

@@ -17,8 +17,6 @@ import AlertState from 'pages/AlertDetails/AlertHeader/AlertState/AlertState';
import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import styles from './Table.module.scss';
const transformLabelsToQbKeys = (
labels: AlertRuleTimelineTableResponse['labels'],
): AttributeKey[] => Object.keys(labels).flatMap((key) => [{ key }]);
@@ -58,7 +56,7 @@ function LabelFilter({
<ClientSideQBSearch
onChange={handleSearch}
filters={filters}
className={styles.alertHistoryLabelSearch}
className="alert-history-label-search"
attributeKeys={transformedKeys}
attributeValuesMap={attributesMap}
suffixIcon={
@@ -90,21 +88,29 @@ export const timelineTableColumns = ({
dataIndex: 'state',
sorter: true,
width: 140,
render: (value): JSX.Element => <AlertState state={value} showLabel />,
render: (value): JSX.Element => (
<div className="alert-rule-state">
<AlertState state={value} showLabel />
</div>
),
},
{
title: (
<LabelFilter setFilters={setFilters} filters={filters} labels={labels} />
),
dataIndex: 'labels',
render: (labels): JSX.Element => <AlertLabels labels={labels} />,
render: (labels): JSX.Element => (
<div className="alert-rule-labels">
<AlertLabels labels={labels} />
</div>
),
},
{
title: 'CREATED AT',
dataIndex: 'unixMilli',
width: 200,
render: (value): JSX.Element => (
<div className={styles.alertRuleCreatedAt}>
<div className="alert-rule__created-at">
{formatTimezoneAdjustedTimestamp(value, DATE_TIME_FORMATS.DASH_DATETIME)}
</div>
),
@@ -119,7 +125,7 @@ export const timelineTableColumns = ({
relatedLogsLink={record.relatedLogsLink}
>
<Button type="text" ghost>
<Ellipsis size="md" />
<Ellipsis className="dropdown-icon" size="md" />
</Button>
</ConditionalAlertPopover>
),

View File

@@ -1,35 +0,0 @@
.timelineTabsAndFilters {
display: flex;
justify-content: space-between;
align-items: center;
}
.resetButton,
.top5Contributors {
display: flex;
align-items: center;
gap: var(--spacing-5);
}
.comingSoon {
display: inline-flex;
padding: var(--spacing-2) var(--spacing-4);
border-radius: 20px;
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
justify-content: center;
align-items: center;
gap: var(--spacing-2);
}
.comingSoonText {
color: var(--text-sienna-400);
font-size: var(--periscope-font-size-small);
font-weight: var(--font-weight-medium);
letter-spacing: -0.05px;
line-height: normal;
}
.comingSoonIcon {
display: flex;
}

View File

@@ -0,0 +1,32 @@
.timeline-tabs-and-filters {
display: flex;
justify-content: space-between;
align-items: center;
.reset-button,
.top-5-contributors {
display: flex;
align-items: center;
gap: 10px;
}
.coming-soon {
display: inline-flex;
padding: 4px 8px;
border-radius: 20px;
border: 1px solid color-mix(in srgb, var(--bg-sienna-500) 20%, transparent);
background: color-mix(in srgb, var(--bg-sienna-500) 10%, transparent);
justify-content: center;
align-items: center;
gap: 5px;
&__text {
color: var(--text-sienna-400);
font-size: 10px;
font-weight: 500;
letter-spacing: -0.05px;
line-height: normal;
}
&__icon {
display: flex;
}
}
}

View File

@@ -6,13 +6,13 @@ import history from 'lib/history';
import { Info } from '@signozhq/icons';
import Tabs2 from 'periscope/components/Tabs2';
import styles from './TabsAndFilters.module.scss';
import './TabsAndFilters.styles.scss';
function ComingSoon(): JSX.Element {
return (
<div className={styles.comingSoon}>
<div className={styles.comingSoonText}>Coming Soon</div>
<div className={styles.comingSoonIcon}>
<div className="coming-soon">
<div className="coming-soon__text">Coming Soon</div>
<div className="coming-soon__icon">
<Info size={10} color={Color.BG_SIENNA_400} />
</div>
</div>
@@ -27,7 +27,7 @@ function TimelineTabs(): JSX.Element {
{
value: TimelineTab.TOP_5_CONTRIBUTORS,
label: (
<div className={styles.top5Contributors}>
<div className="top-5-contributors">
Top 5 Contributors
<ComingSoon />
</div>
@@ -80,7 +80,7 @@ function TimelineFilters(): JSX.Element {
function TabsAndFilters(): JSX.Element {
return (
<div className={styles.timelineTabsAndFilters}>
<div className="timeline-tabs-and-filters">
<TimelineTabs />
<TimelineFilters />
</div>

View File

@@ -1,14 +0,0 @@
.timeline {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
margin: 0 var(--spacing-8);
}
.timelineTitle {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-20);
letter-spacing: -0.07px;
}

View File

@@ -0,0 +1,14 @@
.timeline {
display: flex;
flex-direction: column;
gap: 8px;
margin: 0 16px;
&__title {
color: var(--l1-foreground);
font-size: 14px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.07px;
}
}

View File

@@ -2,7 +2,7 @@ import GraphWrapper from './GraphWrapper/GraphWrapper';
import TimelineTable from './Table/Table';
import TabsAndFilters from './TabsAndFilters/TabsAndFilters';
import styles from './Timeline.module.scss';
import './Timeline.styles.scss';
function TimelineTableRenderer(): JSX.Element {
return <TimelineTable />;
@@ -14,15 +14,15 @@ function Timeline({
totalCurrentTriggers: number;
}): JSX.Element {
return (
<div className={styles.timeline}>
<div className={styles.timelineTitle}>Timeline</div>
<div>
<div className="timeline">
<div className="timeline__title">Timeline</div>
<div className="timeline__tabs-and-filters">
<TabsAndFilters />
</div>
<div>
<div className="timeline__graph">
<GraphWrapper totalCurrentTriggers={totalCurrentTriggers} />
</div>
<div>
<div className="timeline__table">
<TimelineTableRenderer />
</div>
</div>

View File

@@ -1,183 +0,0 @@
.anomalyAlertEvaluationView {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: var(--spacing-4);
width: 100%;
height: 100%;
:global(.uplot-tooltip) {
background-color: rgb(0 0 0 / 90%);
box-shadow: 0 2px 4px rgb(0 0 0 / 10%);
color: #ddd;
font-size: var(--periscope-font-size-base);
line-height: 1.4;
padding: var(--spacing-4) var(--spacing-6);
pointer-events: none;
position: absolute;
z-index: 100;
max-height: 500px;
width: 280px;
overflow-y: auto;
display: none;
&::-webkit-scrollbar {
width: 0.3rem;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgb(136 136 136);
border-radius: 0.625rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
:global(.uplot-tooltip-title) {
font-weight: var(--font-weight-semibold);
margin-bottom: var(--spacing-2);
}
:global(.uplot-tooltip-series) {
display: flex;
gap: var(--spacing-2);
padding: var(--spacing-2) 0;
align-items: center;
}
:global(.uplot-tooltip-series-name) {
margin-right: var(--spacing-2);
}
:global(.uplot-tooltip-band) {
font-style: italic;
color: #666;
}
:global(.uplot-tooltip-marker) {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: var(--spacing-4);
vertical-align: middle;
}
:global(.uplot) {
:global(.u-title) {
text-align: center;
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-normal);
display: flex;
height: 40px;
align-items: center;
}
:global(.u-legend) {
display: flex;
margin-top: var(--spacing-8);
tbody {
width: 100%;
:global(.u-series) {
display: inline-flex;
}
}
}
}
}
.chartSection {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.chartSectionMultiSeries {
composes: chartSection;
width: calc(100% - 240px);
}
.noDataContainer {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: var(--spacing-4);
}
.seriesSelection {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
width: 240px;
padding: 0 var(--spacing-4);
height: 100%;
}
.seriesList {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
height: 100%;
}
.seriesListSearch {
margin-bottom: var(--spacing-8);
}
.seriesListTitle {
margin-top: var(--spacing-6);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-normal);
}
.seriesListItems {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
height: 100%;
overflow-y: auto;
&::-webkit-scrollbar {
width: 0.1rem;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgb(136 136 136);
border-radius: 0.625rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.seriesListItem {
display: flex;
flex-direction: row;
gap: var(--spacing-4);
cursor: pointer;
}
.seriesListItemColor {
width: 6px;
height: 6px;
border-radius: 50%;
display: inline-flex;
margin-right: var(--spacing-4);
vertical-align: middle;
}

View File

@@ -0,0 +1,180 @@
.anomaly-alert-evaluation-view {
display: flex;
flex-direction: row;
justify-content: space-between;
gap: 8px;
width: 100%;
height: 100%;
.anomaly-alert-evaluation-view-chart-section {
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
&.has-multi-series-data {
width: calc(100% - 240px);
}
.anomaly-alert-evaluation-view-no-data-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 8px;
}
}
.anomaly-alert-evaluation-view-series-selection {
display: flex;
flex-direction: column;
gap: 8px;
width: 240px;
padding: 0px 8px;
height: 100%;
.anomaly-alert-evaluation-view-series-list {
display: flex;
flex-direction: column;
gap: 8px;
height: 100%;
.anomaly-alert-evaluation-view-series-list-search {
margin-bottom: 16px;
}
.anomaly-alert-evaluation-view-series-list-title {
margin-top: 12px;
font-size: 13px !important;
font-weight: 400;
}
.anomaly-alert-evaluation-view-series-list-items {
display: flex;
flex-direction: column;
gap: 8px;
height: 100%;
overflow-y: auto;
.anomaly-alert-evaluation-view-series-list-item {
display: flex;
flex-direction: row;
gap: 8px;
.anomaly-alert-evaluation-view-series-list-item-color {
width: 6px;
height: 6px;
border-radius: 50%;
display: inline-flex;
margin-right: 8px;
vertical-align: middle;
}
cursor: pointer;
}
&::-webkit-scrollbar {
width: 0.1rem;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgb(136, 136, 136);
border-radius: 0.625rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
}
}
.uplot {
.u-title {
text-align: center;
font-size: 18px;
font-weight: 400;
display: flex;
height: 40px;
font-size: 13px;
align-items: center;
}
.u-legend {
display: flex;
margin-top: 16px;
tbody {
width: 100%;
.u-series {
display: inline-flex;
}
}
}
}
}
.uplot-tooltip {
background-color: rgba(0, 0, 0, 0.9);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: #ddd;
font-size: 13px;
line-height: 1.4;
padding: 8px 12px;
pointer-events: none;
position: absolute;
z-index: 100;
max-height: 500px;
width: 280px;
overflow-y: auto;
display: none; /* Hide tooltip by default */
&::-webkit-scrollbar {
width: 0.3rem;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgb(136, 136, 136);
border-radius: 0.625rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
}
.uplot-tooltip-title {
font-weight: bold;
margin-bottom: 4px;
}
.uplot-tooltip-series {
display: flex;
gap: 4px;
padding: 4px 0px;
align-items: center;
}
.uplot-tooltip-series-name {
margin-right: 4px;
}
.uplot-tooltip-band {
font-style: italic;
color: #666;
}
.uplot-tooltip-marker {
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 8px;
vertical-align: middle;
}

View File

@@ -15,7 +15,7 @@ import uPlot from 'uplot';
import tooltipPlugin from './tooltipPlugin';
import 'uplot/dist/uPlot.min.css';
import styles from './AnomalyAlertEvaluationView.module.scss';
import './AnomalyAlertEvaluationView.styles.scss';
const { Search } = Input;
@@ -284,11 +284,11 @@ function AnomalyAlertEvaluationView({
}, 300);
return (
<div className={styles.anomalyAlertEvaluationView}>
<div className="anomaly-alert-evaluation-view">
<div
className={
allSeries.length > 1 ? styles.chartSectionMultiSeries : styles.chartSection
}
className={`anomaly-alert-evaluation-view-chart-section ${
allSeries.length > 1 ? 'has-multi-series-data' : ''
}`}
ref={graphRef}
>
{allSeries.length > 0 ? (
@@ -298,7 +298,7 @@ function AnomalyAlertEvaluationView({
chartRef={chartRef}
/>
) : (
<div className={styles.noDataContainer}>
<div className="anomaly-alert-evaluation-view-no-data-container">
<ChartLine size={48} strokeWidth={0.5} />
<Typography>No Data</Typography>
@@ -307,20 +307,20 @@ function AnomalyAlertEvaluationView({
</div>
{allSeries.length > 1 && (
<div className={styles.seriesSelection}>
<div className="anomaly-alert-evaluation-view-series-selection">
{allSeries.length > 1 && (
<div className={styles.seriesList}>
<div className="anomaly-alert-evaluation-view-series-list">
<Search
className={styles.seriesListSearch}
className="anomaly-alert-evaluation-view-series-list-search"
placeholder="Search a series"
allowClear
onChange={handleSearchValueChange}
/>
<div className={styles.seriesListItems}>
<div className="anomaly-alert-evaluation-view-series-list-items">
{filteredSeriesKeys.length > 0 && (
<Checkbox
className={styles.seriesListItem}
className="anomaly-alert-evaluation-view-series-list-item"
name="series"
value={selectedSeries === null}
onChange={(): void => handleSeriesChange(null)}
@@ -332,14 +332,14 @@ function AnomalyAlertEvaluationView({
{filteredSeriesKeys.map((seriesKey) => (
<div key={seriesKey}>
<Checkbox
className={styles.seriesListItem}
className="anomaly-alert-evaluation-view-series-list-item"
key={seriesKey}
name="series"
value={selectedSeries === seriesKey}
onChange={(): void => handleSeriesChange(seriesKey)}
>
<div
className={styles.seriesListItemColor}
className="anomaly-alert-evaluation-view-series-list-item-color"
style={{ backgroundColor: seriesData[seriesKey].color }}
/>

View File

@@ -1,69 +0,0 @@
.createAlertTabsExtra {
display: flex;
align-items: center;
gap: var(--spacing-8);
}
.createAlertWrapper {
margin-top: var(--spacing-5);
:global(.divider) {
border-color: var(--l1-border);
margin: var(--spacing-8) 0;
}
:global(.breadcrumb-divider) {
margin-top: var(--spacing-5);
}
}
.createAlertBreadcrumb {
padding-left: var(--spacing-8);
:global(.breadcrumb-item) {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: var(--line-height-20);
letter-spacing: 0.25px;
padding: 0;
}
:global(.ant-breadcrumb-separator),
:global(.breadcrumb-item--last) {
color: var(--muted-foreground);
font-family: var(--periscope-font-family-mono);
}
}
.createAlertBreadcrumb ol {
align-items: center;
}
.alertsContainer {
:global(.top-level-tab.periscope-tab) {
padding: var(--spacing-1) 0;
}
:global(.ant-tabs-nav) {
padding: 0 var(--spacing-4);
margin-bottom: 0 !important;
&::before {
border-bottom: 1px solid var(--l1-border) !important;
}
}
:global(.ant-tabs-tab) {
&[data-node-key='TriggeredAlerts'] {
margin-left: var(--spacing-8);
}
&:not(:first-of-type) {
margin-left: var(--spacing-10) !important;
}
}
:global(.ant-tabs-tab) [aria-selected='false'] :global(.periscope-tab) {
color: var(--l2-foreground);
}
}

View File

@@ -0,0 +1,75 @@
.create-alert-tabs {
&__extra {
display: flex;
align-items: center;
gap: 16px;
}
}
.create-alert-wrapper {
margin-top: 10px;
.divider {
border-color: var(--l1-border);
margin: 16px 0;
}
.breadcrumb-divider {
margin-top: 10px;
}
}
.create-alert__breadcrumb {
padding-left: 16px;
ol {
align-items: center;
}
.breadcrumb-item {
color: var(--l2-foreground);
font-size: 14px;
line-height: 20px;
letter-spacing: 0.25px;
padding: 0;
}
.ant-breadcrumb-separator,
.breadcrumb-item--last {
color: var(--muted-foreground);
font-family: 'Geist Mono';
}
}
.alerts-container {
.top-level-tab.periscope-tab {
padding: 2px 0;
}
.ant-tabs {
&-nav {
padding: 0 8px;
margin-bottom: 0 !important;
&::before {
border-bottom: 1px solid var(--l1-border) !important;
}
}
&-tab {
&[data-node-key='TriggeredAlerts'] {
margin-left: 16px;
}
&:not(:first-of-type) {
margin-left: 24px !important;
}
[aria-selected='false'] {
.periscope-tab {
color: var(--l2-foreground);
}
}
}
}
}

View File

@@ -1,5 +1,4 @@
import { useCallback, useEffect, useMemo } from 'react';
import cx from 'classnames';
import { Form, Tabs, TabsProps } from 'antd';
import logEvent from 'api/common/logEvent';
import ConfigureIcon from 'assets/AlertHistory/ConfigureIcon';
@@ -23,7 +22,7 @@ import { ALERT_TYPE_VS_SOURCE_MAPPING } from './config';
import { ALERTS_VALUES_MAP, ALERT_TYPE_BREADCRUMB_TITLE } from './defaults';
import SelectAlertType from './SelectAlertType';
import styles from './CreateAlertRule.module.scss';
import './CreateAlertRule.styles.scss';
function CreateRules(): JSX.Element {
const [formInstance] = Form.useForm();
@@ -144,9 +143,9 @@ function CreateRules(): JSX.Element {
),
key: AlertListTabs.ALERT_RULES,
children: (
<div className={styles.createAlertWrapper}>
<div className="create-alert-wrapper">
<AlertBreadcrumb
className={styles.createAlertBreadcrumb}
className="create-alert__breadcrumb"
items={
isTypeSelectionMode
? [
@@ -191,9 +190,9 @@ function CreateRules(): JSX.Element {
items={items}
activeKey={AlertListTabs.ALERT_RULES}
onChange={handleTabChange}
className={cx(styles.alertsContainer, 'create-alert-tabs')}
className="alerts-container create-alert-tabs"
tabBarExtraContent={
<div className={styles.createAlertTabsExtra}>
<div className="create-alert-tabs__extra">
<DateTimeSelector showAutoRefresh />
<HeaderRightSection
enableAnnouncements={false}

View File

@@ -1,66 +0,0 @@
.alertConditionContainer {
margin: 0 var(--spacing-8);
margin-top: var(--spacing-12);
}
.alertCondition {
display: flex;
align-items: center;
margin-left: var(--spacing-6);
margin-top: var(--spacing-12);
}
.alertConditionTabs {
display: flex;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
flex-direction: row;
border-bottom: none;
margin-bottom: -1px;
}
.explorerViewOption {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
border: none;
padding: 9px;
box-shadow: none;
border-radius: 0;
border-left: 0.5px solid var(--l1-border);
border-bottom: 0.5px solid var(--l1-border);
width: 120px;
height: 36px;
gap: var(--spacing-4);
&:first-child {
border-left: 1px solid transparent;
}
&:hover {
background-color: transparent !important;
border-left: 1px solid transparent !important;
color: var(--l1-foreground);
}
&:disabled {
background-color: var(--l2-background);
opacity: 0.6;
}
}
.activeTab {
background-color: var(--l1-background);
border-bottom: none;
&:hover {
background-color: var(--l1-background) !important;
}
}
.condensedAdvancedOptionsContainer {
margin-top: var(--spacing-8);
width: fit-parent;
}

View File

@@ -15,7 +15,7 @@ import AlertThreshold from './AlertThreshold';
import AnomalyThreshold from './AnomalyThreshold';
import { ANOMALY_TAB_TOOLTIP, THRESHOLD_TAB_TOOLTIP } from './constants';
import styles from './AlertCondition.module.scss';
import './styles.scss';
function AlertCondition(): JSX.Element {
const { alertType, setAlertType } = useCreateAlertState();
@@ -67,15 +67,15 @@ function AlertCondition(): JSX.Element {
};
return (
<div className={styles.alertConditionContainer}>
<div className="alert-condition-container">
<Stepper stepNumber={2} label="Set alert conditions" />
<div className={styles.alertCondition}>
<div className={styles.alertConditionTabs}>
<div className="alert-condition">
<div className="alert-condition-tabs">
{tabs.map((tab) => (
<Tooltip key={tab.value} title={getTabTooltip(tab)}>
<Button
className={classNames(styles.explorerViewOption, {
[styles.activeTab]: alertType === tab.value,
className={classNames('list-view-tab', 'explorer-view-option', {
'active-tab': alertType === tab.value,
})}
onClick={(): void => {
if (alertType !== tab.value) {
@@ -106,7 +106,7 @@ function AlertCondition(): JSX.Element {
refreshChannels={refreshChannels}
/>
)}
<div className={styles.condensedAdvancedOptionsContainer}>
<div className="condensed-advanced-options-container">
<AdvancedOptions />
</div>
</div>

View File

@@ -1,84 +0,0 @@
.alertThresholdContainer {
padding: var(--spacing-12);
padding-right: 72px;
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
width: 100%;
}
.alertConditionSentences {
display: flex;
flex-direction: column;
gap: var(--spacing-6);
}
.alertConditionSentence {
display: flex;
align-items: center;
gap: var(--spacing-8);
flex-wrap: wrap;
:global(.ant-select) {
width: 240px;
:global(.ant-select-selector) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--muted-foreground);
font-family: 'Space Mono';
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--muted-foreground);
}
}
}
.sentenceText {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: 1.5;
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}
.thresholdsSection {
margin-top: var(--spacing-8);
margin-left: var(--spacing-12);
}
.addThresholdBtn {
margin-top: var(--spacing-4);
border: 1px dashed var(--l1-border);
color: var(--l2-foreground);
background-color: transparent;
border-radius: 4px;
height: 32px;
padding: 0 var(--spacing-8);
display: flex;
align-items: center;
justify-content: center;
&:hover {
border-color: var(--l1-border);
color: var(--l1-foreground);
}
:global(.anticon) {
margin-right: var(--spacing-4);
}
}

View File

@@ -1,6 +1,7 @@
import { useEffect } from 'react';
import { Button, Select, Tooltip } from 'antd';
import { Typography } from '@signozhq/ui/typography';
import classNames from 'classnames';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import getRandomColor from 'lib/getRandomColor';
import { Plus } from '@signozhq/icons';
@@ -31,7 +32,8 @@ import {
RoutingPolicyBanner,
} from './utils';
import styles from './AlertThreshold.module.scss';
import './styles.scss';
import '../EvaluationSettings/styles.scss';
function AlertThreshold({
channels,
@@ -217,11 +219,16 @@ function AlertThreshold({
};
return (
<div className={styles.alertThresholdContainer}>
<div
className={classNames(
'alert-threshold-container',
'condensed-alert-threshold-container',
)}
>
{/* Main condition sentence */}
<div className={styles.alertConditionSentences}>
<div className={styles.alertConditionSentence}>
<Typography.Text className={styles.sentenceText}>
<div className="alert-condition-sentences">
<div className="alert-condition-sentence">
<Typography.Text className="sentence-text">
Send a notification when
</Typography.Text>
<Select
@@ -231,7 +238,7 @@ function AlertThreshold({
options={queryNames}
data-testid="alert-threshold-query-select"
/>
<Typography.Text className={styles.sentenceText}>is</Typography.Text>
<Typography.Text className="sentence-text">is</Typography.Text>
<Select
value={
(normalizeOperator(thresholdState.operator) ??
@@ -247,7 +254,7 @@ function AlertThreshold({
options={THRESHOLD_OPERATOR_OPTIONS}
data-testid="alert-threshold-operator-select"
/>
<Typography.Text className={styles.sentenceText}>
<Typography.Text className="sentence-text">
the threshold(s)
</Typography.Text>
<Select
@@ -265,13 +272,13 @@ function AlertThreshold({
options={matchTypeOptionsWithTooltips}
data-testid="alert-threshold-match-type-select"
/>
<Typography.Text className={styles.sentenceText}>
<Typography.Text className="sentence-text">
during the <EvaluationSettings />
</Typography.Text>
</div>
</div>
<div className={styles.thresholdsSection}>
<div className="thresholds-section">
{thresholdState.thresholds.map((threshold, index) => (
<ThresholdItem
key={threshold.id}
@@ -290,7 +297,7 @@ function AlertThreshold({
type="dashed"
icon={<Plus size={16} />}
onClick={addThreshold}
className={styles.addThresholdBtn}
className="add-threshold-btn"
data-testid="add-threshold-button"
>
Add Threshold

View File

@@ -1,63 +0,0 @@
.anomalyThresholdContainer {
padding: var(--spacing-12);
padding-right: 72px;
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
width: 100%;
:global(.ant-select) {
:global(.ant-select-selector) {
min-width: 150px;
}
}
}
.alertConditionSentences {
display: flex;
flex-direction: column;
gap: var(--spacing-6);
}
.alertConditionSentence {
display: flex;
align-items: center;
gap: var(--spacing-8);
flex-wrap: wrap;
:global(.ant-select) {
width: 240px;
:global(.ant-select-selector) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--muted-foreground);
font-family: 'Space Mono';
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--muted-foreground);
}
}
}
.sentenceText {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: 1.5;
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}

View File

@@ -24,8 +24,6 @@ import {
RoutingPolicyBanner,
} from './utils';
import styles from './AnomalyThreshold.module.scss';
function AnomalyThreshold({
channels,
isLoadingChannels,
@@ -66,14 +64,11 @@ function AnomalyThreshold({
};
return (
<div className={styles.anomalyThresholdContainer}>
<div className={styles.alertConditionSentences}>
<div className="anomaly-threshold-container">
<div className="alert-condition-sentences">
{/* Sentence 1 */}
<div className={styles.alertConditionSentence}>
<Typography.Text
data-testid="notification-text"
className={styles.sentenceText}
>
<div className="alert-condition-sentence">
<Typography.Text data-testid="notification-text" className="sentence-text">
Send notification when the observed value for
</Typography.Text>
<Select
@@ -89,7 +84,7 @@ function AnomalyThreshold({
/>
<Typography.Text
data-testid="evaluation-window-text"
className={styles.sentenceText}
className="sentence-text"
>
during the last
</Typography.Text>
@@ -105,12 +100,9 @@ function AnomalyThreshold({
options={ANOMALY_TIME_DURATION_OPTIONS}
/>
</div>
<div className={styles.alertConditionSentence}>
<div className="alert-condition-sentence">
{/* Sentence 2 */}
<Typography.Text
data-testid="threshold-text"
className={styles.sentenceText}
>
<Typography.Text data-testid="threshold-text" className="sentence-text">
is
</Typography.Text>
<Select
@@ -125,10 +117,7 @@ function AnomalyThreshold({
}}
options={deviationOptions}
/>
<Typography.Text
data-testid="deviations-text"
className={styles.sentenceText}
>
<Typography.Text data-testid="deviations-text" className="sentence-text">
deviations
</Typography.Text>
<Select
@@ -147,7 +136,7 @@ function AnomalyThreshold({
/>
<Typography.Text
data-testid="predicted-data-text"
className={styles.sentenceText}
className="sentence-text"
>
the predicted data
</Typography.Text>
@@ -167,11 +156,8 @@ function AnomalyThreshold({
/>
</div>
{/* Sentence 3 */}
<div className={styles.alertConditionSentence}>
<Typography.Text
data-testid="using-the-text"
className={styles.sentenceText}
>
<div className="alert-condition-sentence">
<Typography.Text data-testid="using-the-text" className="sentence-text">
using the
</Typography.Text>
<Select
@@ -187,7 +173,7 @@ function AnomalyThreshold({
/>
<Typography.Text
data-testid="algorithm-with-text"
className={styles.sentenceText}
className="sentence-text"
>
algorithm with
</Typography.Text>
@@ -206,7 +192,7 @@ function AnomalyThreshold({
<>
<Typography.Text
data-testid="seasonality-text"
className={styles.sentenceText}
className="sentence-text"
>
seasonality to
</Typography.Text>
@@ -242,10 +228,7 @@ function AnomalyThreshold({
/>
</>
) : (
<Typography.Text
data-testid="seasonality-text"
className={styles.sentenceText}
>
<Typography.Text data-testid="seasonality-text" className="sentence-text">
seasonality
</Typography.Text>
)}

View File

@@ -1,105 +0,0 @@
.thresholdItem {
display: flex;
flex-direction: column;
gap: 0;
margin-bottom: var(--spacing-8);
}
.thresholdRow {
display: flex;
align-items: center;
gap: var(--spacing-8);
margin-bottom: 2px;
}
.thresholdIndicator {
display: flex;
}
.thresholdDot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
.thresholdControls {
display: flex;
align-items: center;
gap: var(--spacing-4);
flex-wrap: wrap;
:global(.ant-input) {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
:global(.ant-select) {
:global(.ant-select-selector) {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
:global(.ant-select-selection-placeholder) {
font-family: 'Space Mono';
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--muted-foreground);
}
}
}
.iconBtn {
color: var(--muted-foreground);
border: 1px solid var(--l1-border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.sentenceText {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
line-height: 1.5;
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}
.highlightedText {
font-weight: bold;
color: var(--bg-robin-400);
margin: 0 4px;
}

View File

@@ -11,8 +11,6 @@ import { normalizeOperator } from '../utils';
import { ThresholdItemProps } from './types';
import { NotificationChannelsNotFoundContent } from './utils';
import styles from './ThresholdItem.module.scss';
function ThresholdItem({
threshold,
updateThreshold,
@@ -84,16 +82,15 @@ function ThresholdItem({
};
return (
<div key={threshold.id} className={styles.thresholdItem}>
<div className={styles.thresholdRow}>
<div className={styles.thresholdIndicator}>
<div key={threshold.id} className="threshold-item">
<div className="threshold-row">
<div className="threshold-indicator">
<div
className={styles.thresholdDot}
className="threshold-dot"
style={{ backgroundColor: threshold.color }}
data-testid="threshold-dot"
/>
</div>
<div className={styles.thresholdControls}>
<div className="threshold-controls">
<Input
placeholder="Enter threshold name"
value={threshold.label}
@@ -103,10 +100,8 @@ function ThresholdItem({
style={{ width: 200 }}
data-testid="threshold-name-input"
/>
<Typography.Text className={styles.sentenceText}>on value</Typography.Text>
<Typography.Text
className={`${styles.sentenceText} ${styles.highlightedText}`}
>
<Typography.Text className="sentence-text">on value</Typography.Text>
<Typography.Text className="sentence-text highlighted-text">
{getOperatorSymbol()}
</Typography.Text>
<Input
@@ -122,9 +117,7 @@ function ThresholdItem({
{yAxisUnitSelect}
{!notificationSettings.routingPolicies && (
<>
<Typography.Text className={styles.sentenceText}>
send to
</Typography.Text>
<Typography.Text className="sentence-text">send to</Typography.Text>
<Select
value={threshold.channels}
onChange={(value): void =>
@@ -161,9 +154,7 @@ function ThresholdItem({
)}
{showRecoveryThreshold && (
<>
<Typography.Text className={styles.sentenceText}>
recover on
</Typography.Text>
<Typography.Text className="sentence-text">recover on</Typography.Text>
<Input
placeholder="Enter recovery threshold value"
value={threshold.recoveryThresholdValue ?? ''}
@@ -179,7 +170,7 @@ function ThresholdItem({
type="default"
icon={<Trash size={16} />}
onClick={removeRecoveryThreshold}
className={styles.iconBtn}
className="icon-btn"
data-testid="remove-recovery-threshold-button"
/>
</Tooltip>
@@ -203,7 +194,7 @@ function ThresholdItem({
type="default"
icon={<CircleX size={16} />}
onClick={(): void => removeThreshold(threshold.id)}
className={styles.iconBtn}
className="icon-btn"
data-testid="remove-threshold-button"
/>
</Tooltip>

View File

@@ -25,6 +25,7 @@ const THRESHOLD_VIEW_TEST_ID = 'threshold-view';
const ANOMALY_VIEW_TEST_ID = 'anomaly-view';
const ANOMALY_TAB_TEXT = 'Anomaly';
const THRESHOLD_TAB_TEXT = 'Threshold';
const ACTIVE_TAB_CLASS = '.active-tab';
// Mock the Stepper component
jest.mock('../../Stepper', () => ({
@@ -129,9 +130,9 @@ describe('AlertCondition', () => {
// screen.queryByTestId(ANOMALY_THRESHOLD_TEST_ID),
// ).not.toBeInTheDocument();
// Verify threshold tab exists
// Verify threshold tab is active by default
const thresholdTab = screen.getByText(THRESHOLD_TAB_TEXT);
expect(thresholdTab).toBeInTheDocument();
expect(thresholdTab.closest(ACTIVE_TAB_CLASS)).toBeInTheDocument();
// Verify both tabs are visible (METRICS_BASED_ALERT supports multiple tabs)
expect(screen.getByText(THRESHOLD_TAB_TEXT)).toBeInTheDocument();
@@ -205,24 +206,22 @@ describe('AlertCondition', () => {
});
// TODO: Unskip this when anomaly tab is implemented
// Note: Active tab styling is verified through component behavior (correct content shown)
// rather than CSS class checks since CSS modules classes are mocked in tests
it.skip('applies active tab styling correctly', () => {
renderAlertCondition();
// Threshold tab should be active by default - verify by checking content
expect(screen.getByTestId(ALERT_THRESHOLD_TEST_ID)).toBeInTheDocument();
expect(
screen.queryByTestId(ANOMALY_THRESHOLD_TEST_ID),
).not.toBeInTheDocument();
const thresholdTab = screen.getByText(THRESHOLD_TAB_TEXT);
const anomalyTab = screen.getByText(ANOMALY_TAB_TEXT);
// Threshold tab should be active by default
expect(thresholdTab.closest(ACTIVE_TAB_CLASS)).toBeInTheDocument();
expect(anomalyTab.closest(ACTIVE_TAB_CLASS)).not.toBeInTheDocument();
// Click anomaly tab
const anomalyTab = screen.getByText(ANOMALY_TAB_TEXT);
fireEvent.click(anomalyTab);
// Anomaly tab should be active now - verify by checking content
expect(screen.getByTestId(ANOMALY_THRESHOLD_TEST_ID)).toBeInTheDocument();
expect(screen.queryByTestId(ALERT_THRESHOLD_TEST_ID)).not.toBeInTheDocument();
// Anomaly tab should be active now
expect(anomalyTab.closest(ACTIVE_TAB_CLASS)).toBeInTheDocument();
expect(thresholdTab.closest(ACTIVE_TAB_CLASS)).not.toBeInTheDocument();
});
it('shows multiple tabs for METRICS_BASED_ALERT', () => {

View File

@@ -126,8 +126,8 @@ describe('ThresholdItem', () => {
it('renders threshold indicator with correct color', () => {
renderThresholdItem();
// Find the threshold dot by data-testid
const thresholdDot = screen.getByTestId('threshold-dot');
// Find the threshold dot by its class
const thresholdDot = document.querySelector('.threshold-dot');
expect(thresholdDot).toHaveStyle('background-color: #ff0000');
});

View File

@@ -0,0 +1,406 @@
.alert-condition-container {
margin: 0 16px;
margin-top: 24px;
.alert-condition {
display: flex;
align-items: center;
margin-left: 12px;
margin-top: 24px;
.alert-condition-tabs {
display: flex;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
flex-direction: row;
border-bottom: none;
margin-bottom: -1px;
.explorer-view-option {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
border: none;
padding: 9px;
box-shadow: none;
border-radius: 0px;
border-left: 0.5px solid var(--l1-border);
border-bottom: 0.5px solid var(--l1-border);
width: 120px;
height: 36px;
gap: 8px;
&.active-tab {
background-color: var(--l1-background);
border-bottom: none;
&:hover {
background-color: var(--l1-background) !important;
}
}
&:disabled {
background-color: var(--l2-background);
opacity: 0.6;
}
&:first-child {
border-left: 1px solid transparent;
}
&:hover {
background-color: transparent !important;
border-left: 1px solid transparent !important;
color: var(--l1-foreground);
}
}
}
}
}
.alert-threshold-container,
.anomaly-threshold-container {
padding: 24px;
padding-right: 72px;
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
width: 100%;
.alert-condition-sentences {
display: flex;
flex-direction: column;
gap: 12px;
.alert-condition-sentence {
display: flex;
align-items: center;
gap: 16px;
flex-wrap: wrap;
.sentence-text {
color: var(--l2-foreground);
font-size: 13px;
line-height: 1.5;
display: flex;
align-items: center;
gap: 8px;
}
.ant-select {
width: 240px;
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--muted-foreground);
font-family: 'Space Mono';
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select-selection-item {
color: var(--l1-foreground);
}
.ant-select-arrow {
color: var(--muted-foreground);
}
}
}
}
.thresholds-section {
margin-top: 16px;
margin-left: 24px;
.threshold-item {
display: flex;
flex-direction: column;
gap: 0;
margin-bottom: 16px;
.threshold-row {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 2px;
.threshold-indicator {
.threshold-dot {
width: 12px;
height: 12px;
border-radius: 50%;
flex-shrink: 0;
}
}
.threshold-controls {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
.ant-input {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select {
.ant-select-selector {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
.ant-select-selection-placeholder {
font-family: 'Space Mono';
}
}
.ant-select-selection-item {
color: var(--l1-foreground);
}
.ant-select-arrow {
color: var(--muted-foreground);
}
}
.icon-btn {
color: var(--muted-foreground);
border: 1px solid var(--l1-border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
}
}
.recovery-threshold-input-group {
display: flex;
align-items: center;
gap: 0;
margin-left: 28px;
.recovery-threshold-label {
pointer-events: none;
cursor: default;
}
.recovery-threshold-btn {
pointer-events: none;
cursor: default;
color: var(--muted-foreground);
background-color: var(--card) !important;
border: 1px solid var(--l1-border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
}
.ant-input {
background-color: var(--card);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
}
}
.add-threshold-btn {
margin-top: 8px;
border: 1px dashed var(--l1-border);
color: var(--l2-foreground);
background-color: transparent;
border-radius: 4px;
height: 32px;
padding: 0 16px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
border-color: var(--l1-border);
color: var(--l1-foreground);
}
.anticon {
margin-right: 8px;
}
}
}
.routing-policies-info-banner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
margin-top: 16px;
background-color: color-mix(
in srgb,
var(--primary-background) 10%,
transparent
);
border: 1px solid var(--primary-background);
padding: 8px 16px;
.routing-policies-info-banner-right {
display: flex;
align-items: center;
gap: 8px;
.view-routing-policies-button {
color: var(--accent-primary);
font-size: 12px;
font-weight: 500;
display: flex;
align-items: center;
gap: 4px;
}
}
.ant-typography {
color: var(--accent-primary);
}
}
}
.anomaly-threshold-container {
.ant-select {
.ant-select-selector {
min-width: 150px;
}
}
}
.condensed-alert-threshold-container,
.condensed-anomaly-threshold-container {
width: 100%;
}
.condensed-advanced-options-container {
margin-top: 16px;
width: fit-parent;
}
.condensed-evaluation-settings-container {
.ant-btn {
display: flex;
align-items: center;
min-width: 240px;
width: auto;
justify-content: space-between;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
.evaluate-alert-conditions-button-left {
color: var(--l2-foreground);
font-size: 12px;
flex-shrink: 0;
}
.evaluate-alert-conditions-button-right {
display: flex;
align-items: center;
color: var(--l2-foreground);
gap: 8px;
flex-shrink: 0;
.evaluate-alert-conditions-button-right-text {
font-size: 12px;
font-weight: 500;
background-color: var(--l1-border);
padding: 1px 4px;
}
}
}
}
.highlighted-text {
font-weight: bold;
color: var(--bg-robin-400);
margin: 0 4px;
}
// Tooltip styles
.tooltip-content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
.tooltip-description {
margin-bottom: 8px;
span {
font-weight: bold;
color: var(--bg-robin-400);
}
}
.tooltip-example {
margin-bottom: 8px;
color: var(--l2-foreground);
}
.tooltip-link {
.tooltip-link-text {
color: var(--accent-primary);
font-size: 11px;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
}
}

View File

@@ -1,68 +0,0 @@
.routingPoliciesInfoBanner {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-4);
margin-top: var(--spacing-8);
background-color: color-mix(
in srgb,
var(--primary-background) 10%,
transparent
);
border: 1px solid var(--primary-background);
padding: var(--spacing-4) var(--spacing-8);
:global(.ant-typography) {
color: var(--accent-primary);
}
}
.routingPoliciesInfoBannerRight {
display: flex;
align-items: center;
gap: var(--spacing-4);
}
.viewRoutingPoliciesButton {
color: var(--accent-primary);
font-size: var(--periscope-font-size-small);
font-weight: 500;
display: flex;
align-items: center;
gap: 4px;
}
.tooltipContent {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
}
.tooltipExample {
margin-bottom: var(--spacing-4);
color: var(--l2-foreground);
}
.tooltipLink {
display: block;
}
.tooltipLinkText {
color: var(--accent-primary);
font-size: var(--periscope-font-size-small);
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.tooltipDescription {
margin-bottom: var(--spacing-4);
span {
font-weight: bold;
color: var(--bg-robin-400);
}
}

View File

@@ -22,8 +22,6 @@ import { openInNewTab } from 'utils/navigation';
import { ROUTING_POLICIES_ROUTE } from './constants';
import { RoutingPolicyBannerProps } from './types';
import styles from './utils.module.scss';
export function getQueryNames(currentQuery: Query): BaseOptionType[] {
const involvedQueriesInTraceOperator = getInvolvedQueriesInTraceOperator(
currentQuery.builder.queryTraceOperator,
@@ -185,7 +183,7 @@ function TooltipContent({
handleTooltipClick(e);
}
}}
className={styles.tooltipContent}
className="tooltip-content"
>
{children}
</div>
@@ -206,7 +204,7 @@ function TooltipExample({
matchType: AlertThresholdMatchType;
}): JSX.Element {
return (
<div className={styles.tooltipExample}>
<div className="tooltip-example">
<strong>Example:</strong>
<br />
Say, For a 5-minute window (configured in Evaluation settings), 1 min
@@ -222,12 +220,12 @@ function TooltipExample({
function TooltipLink(): JSX.Element {
return (
<div className={styles.tooltipLink}>
<div className="tooltip-link">
<a
href="https://signoz.io/docs"
target="_blank"
rel="noopener noreferrer"
className={styles.tooltipLinkText}
className="tooltip-link-text"
>
Learn more
</a>
@@ -263,7 +261,7 @@ export const getMatchTypeTooltip = (
case AlertThresholdMatchType.AT_LEAST_ONCE:
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers if <span>ANY</span> of
those aggregated data points crosses the threshold.
@@ -284,7 +282,7 @@ export const getMatchTypeTooltip = (
case AlertThresholdMatchType.ALL_THE_TIME:
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers if <span>ALL</span>{' '}
aggregated data points cross the threshold.
@@ -308,7 +306,7 @@ export const getMatchTypeTooltip = (
).toFixed(1);
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers if the{' '}
<span>AVERAGE</span> of all aggregated data points crosses the threshold.
@@ -330,7 +328,7 @@ export const getMatchTypeTooltip = (
const total = dataPoints.reduce((a, b) => a + b, 0);
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers if the{' '}
<span>SUM</span> of all aggregated data points crosses the threshold.
@@ -352,7 +350,7 @@ export const getMatchTypeTooltip = (
const lastPoint = dataPoints[dataPoints.length - 1];
return (
<TooltipContent>
<div className={styles.tooltipDescription}>
<div className="tooltip-description">
Data is aggregated at each interval within your evaluation window,
creating multiple data points. This option triggers based on the{' '}
<span>MOST RECENT</span> aggregated data point only.
@@ -416,11 +414,11 @@ export function RoutingPolicyBanner({
}: RoutingPolicyBannerProps): JSX.Element {
const { safeNavigate } = useSafeNavigate();
return (
<div className={styles.routingPoliciesInfoBanner}>
<div className="routing-policies-info-banner">
<Typography.Text>
Use <strong>Routing Policies</strong> for dynamic routing
</Typography.Text>
<div className={styles.routingPoliciesInfoBannerRight}>
<div className="routing-policies-info-banner-right">
<Switch
value={notificationSettings.routingPolicies}
testId="routing-policies-switch"
@@ -433,7 +431,7 @@ export function RoutingPolicyBanner({
/>
<Button
type="link"
className={styles.viewRoutingPoliciesButton}
className="view-routing-policies-button"
data-testid="view-routing-policies-button"
onClick={(): void => safeNavigate(ROUTING_POLICIES_ROUTE)}
>

View File

@@ -1,139 +0,0 @@
.alertHeader {
background-color: var(--l1-background);
font-family: inherit;
color: var(--l1-foreground);
padding: var(--spacing-6) var(--spacing-8);
}
.editAlertHeader {
flex: 1;
}
.tabBar {
display: flex;
align-items: center;
justify-content: space-between;
}
.tab {
display: flex;
align-items: center;
background-color: var(--l1-background);
height: 32px;
font-size: var(--periscope-font-size-base);
color: var(--l1-foreground);
&::before {
content: '';
margin-right: var(--spacing-3);
font-size: var(--periscope-font-size-base);
color: var(--l3-foreground);
}
}
.content {
padding: var(--spacing-4) 0;
background: var(--l1-background);
display: flex;
flex-direction: column;
gap: var(--spacing-4);
min-width: 300px;
flex: 1;
}
.inputTitle {
background-color: transparent;
color: var(--l1-foreground);
width: 100%;
min-width: 300px;
}
.inputDescription {
font-size: var(--periscope-font-size-base);
background-color: transparent;
color: var(--l2-foreground);
}
.labelsInput {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
}
.labelsInputAddButton {
width: fit-content;
font-size: var(--periscope-font-size-base);
color: var(--l2-foreground);
border: 1px solid var(--l1-border);
background-color: transparent;
cursor: pointer;
padding: var(--spacing-2) var(--spacing-4);
border-radius: 4px;
&:hover {
border-color: var(--l1-border);
color: var(--l1-foreground);
}
}
.labelsInputExistingLabels {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-4);
}
.labelsInputLabelPill {
display: inline-flex;
align-items: center;
gap: var(--spacing-3);
background-color: #ad7f581a;
color: var(--bg-sienna-400);
padding: var(--spacing-2) var(--spacing-4);
border-radius: 16px;
font-size: var(--periscope-font-size-small);
border: 1px solid var(--bg-sienna-500);
font-family: 'Geist Mono';
}
.labelsInputRemoveButton {
background: none;
border: none;
color: var(--bg-sienna-400);
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
&:hover {
color: var(--l1-foreground);
}
}
.labelsInputInputContainer {
display: flex;
align-items: center;
background-color: transparent;
border: none;
}
.labelsInputInput {
flex: 1;
background-color: transparent;
border: none;
outline: none;
padding: var(--spacing-3) var(--spacing-4);
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
&::placeholder {
color: var(--l2-foreground);
}
&:focus,
&:active {
border: none;
outline: none;
}
}

View File

@@ -2,7 +2,7 @@ import { useCallback, useMemo } from 'react';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import logEvent from 'api/common/logEvent';
import cx from 'classnames';
import classNames from 'classnames';
import { QueryParams } from 'constants/query';
import ROUTES from 'constants/routes';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
@@ -14,7 +14,8 @@ import { Labels } from 'types/api/alerts/def';
import { useCreateAlertState } from '../context';
import LabelsInput from './LabelsInput';
import styles from './CreateAlertHeader.module.scss';
import './styles.scss';
function CreateAlertHeader(): JSX.Element {
const { alertState, setAlertState, isEditMode } = useCreateAlertState();
@@ -55,11 +56,11 @@ function CreateAlertHeader(): JSX.Element {
return (
<div
className={cx(styles.alertHeader, { [styles.editAlertHeader]: isEditMode })}
className={classNames('alert-header', { 'edit-alert-header': isEditMode })}
>
{!isEditMode && (
<div className={styles.tabBar}>
<div className={styles.tab}>New Alert Rule</div>
<div className="alert-header__tab-bar">
<div className="alert-header__tab">New Alert Rule</div>
<Button
prefix={<RotateCcw size={12} />}
onClick={handleSwitchToClassicExperience}
@@ -71,7 +72,7 @@ function CreateAlertHeader(): JSX.Element {
</Button>
</div>
)}
<div className={styles.content}>
<div className="alert-header__content">
<Input
type="text"
value={alertState.name}
@@ -82,7 +83,7 @@ function CreateAlertHeader(): JSX.Element {
alertRuleContext.setAlertRuleName(newName);
}
}}
className={styles.inputTitle}
className="alert-header__input title"
placeholder="Enter alert rule name"
data-testid="alert-name-input"
/>

View File

@@ -3,7 +3,6 @@ import { X } from '@signozhq/icons';
import { useNotifications } from 'hooks/useNotifications';
import { LabelInputState, LabelsInputProps } from './types';
import styles from './CreateAlertHeader.module.scss';
function LabelsInput({
labels,
@@ -121,19 +120,19 @@ function LabelsInput({
}, [inputState]);
return (
<div className={styles.labelsInput}>
<div className="labels-input">
{Object.keys(labels).length > 0 && (
<div className={styles.labelsInputExistingLabels}>
<div className="labels-input__existing-labels">
{Object.entries(labels).map(([key, value]) => (
<span
key={key}
className={styles.labelsInputLabelPill}
className="labels-input__label-pill"
data-testid={`label-pill-${key}-${value}`}
>
{key}: {value}
<button
type="button"
className={styles.labelsInputRemoveButton}
className="labels-input__remove-button"
aria-label={`Remove label ${key}`}
onClick={(): void => handleRemoveLabel(key)}
>
@@ -146,7 +145,7 @@ function LabelsInput({
{!isAdding ? (
<button
className={styles.labelsInputAddButton}
className="labels-input__add-button"
type="button"
onClick={handleAddLabelsClick}
data-testid="alert-add-label-button"
@@ -154,7 +153,7 @@ function LabelsInput({
+ Add labels
</button>
) : (
<div className={styles.labelsInputInputContainer}>
<div className="labels-input__input-container">
<input
autoFocus
type="text"
@@ -162,7 +161,7 @@ function LabelsInput({
onChange={handleInputChange}
onKeyDown={handleKeyDown}
onBlur={handleBlur}
className={styles.labelsInputInput}
className="labels-input__input"
placeholder={inputState.isKeyInput ? 'Enter key' : 'Enter value'}
data-testid="alert-add-label-input"
/>

View File

@@ -0,0 +1,145 @@
.alert-header {
background-color: var(--l1-background);
font-family: inherit;
color: var(--l1-foreground);
padding: 12px 16px;
&__tab-bar {
display: flex;
align-items: center;
justify-content: space-between;
}
/* Tab block visuals */
&__tab {
display: flex;
align-items: center;
background-color: var(--l1-background);
height: 32px;
font-size: 13px;
color: var(--l1-foreground);
}
&__tab::before {
content: '';
margin-right: 6px;
font-size: 13px;
color: var(--l3-foreground);
}
&__content {
padding: 8px 0;
background: var(--l1-background);
display: flex;
flex-direction: column;
gap: 8px;
min-width: 300px;
flex: 1;
}
&__input.title {
background-color: transparent;
color: var(--l1-foreground);
width: 100%;
min-width: 300px;
}
&__input.description {
font-size: 13px;
background-color: transparent;
color: var(--l2-foreground);
}
.ant-btn {
display: flex;
gap: 4px;
align-items: center;
color: var(--l1-foreground);
border: 1px solid var(--l1-border);
margin-right: 16px;
}
}
.labels-input {
display: flex;
flex-direction: column;
gap: 8px;
&__add-button {
width: fit-content;
font-size: 13px;
color: var(--l2-foreground);
border: 1px solid var(--l1-border);
background-color: transparent;
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
&:hover {
border-color: var(--l1-border);
color: var(--l1-foreground);
}
}
&__existing-labels {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
&__label-pill {
display: inline-flex;
align-items: center;
gap: 6px;
background-color: #ad7f581a;
color: var(--bg-sienna-400);
padding: 4px 8px;
border-radius: 16px;
font-size: 12px;
border: 1px solid var(--bg-sienna-500);
font-family: 'Geist Mono';
}
&__remove-button {
background: none;
border: none;
color: var(--bg-sienna-400);
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
&:hover {
color: var(--l1-foreground);
}
}
&__input-container {
display: flex;
align-items: center;
background-color: transparent;
border: none;
}
&__input {
flex: 1;
background-color: transparent;
border: none;
outline: none;
padding: 6px 8px;
color: var(--l1-foreground);
font-size: 13px;
&::placeholder {
color: var(--l2-foreground);
}
&:focus,
&:active {
border: none;
outline: none;
}
}
}

View File

@@ -1,14 +1,14 @@
.createAlertV2Container {
.create-alert-v2-container {
background-color: var(--l1-background);
padding-bottom: 100px;
}
.stickyPageSpinner {
.sticky-page-spinner {
position: fixed;
inset: 0;
display: grid;
place-items: center;
background: rgb(0 0 0 / 35%);
background: rgba(0, 0, 0, 0.35);
z-index: 10000;
pointer-events: auto;
}

View File

@@ -12,7 +12,7 @@ import QuerySection from './QuerySection';
import { CreateAlertV2Props } from './types';
import { Spinner } from './utils';
import styles from './CreateAlertV2.module.scss';
import './CreateAlertV2.styles.scss';
function CreateAlertV2({ alertType }: CreateAlertV2Props): JSX.Element {
const queryToRedirect = buildInitialAlertDef(alertType);
@@ -25,7 +25,7 @@ function CreateAlertV2({ alertType }: CreateAlertV2Props): JSX.Element {
return (
<CreateAlertProvider initialAlertType={alertType}>
<Spinner />
<div className={styles.createAlertV2Container}>
<div className="create-alert-v2-container">
<CreateAlertHeader />
<QuerySection />
<AlertCondition />

View File

@@ -5,7 +5,8 @@ import { Typography } from '@signozhq/ui/typography';
import { Info } from '@signozhq/icons';
import { IAdvancedOptionItemProps } from '../types';
import styles from './styles.module.scss';
import './styles.scss';
function AdvancedOptionItem({
title,
@@ -28,9 +29,9 @@ function AdvancedOptionItem({
};
return (
<div className={styles.advancedOptionItem} data-testid={dataTestId}>
<div className={styles.advancedOptionItemLeftContent}>
<Typography.Text className={styles.advancedOptionItemTitle}>
<div className="advanced-option-item" data-testid={dataTestId}>
<div className="advanced-option-item-left-content">
<Typography.Text className="advanced-option-item-title">
{title}
{tooltipText && (
<Tooltip title={tooltipText}>
@@ -38,13 +39,13 @@ function AdvancedOptionItem({
</Tooltip>
)}
</Typography.Text>
<Typography.Text className={styles.advancedOptionItemDescription}>
<Typography.Text className="advanced-option-item-description">
{description}
</Typography.Text>
</div>
<div className={styles.advancedOptionItemRightContent}>
<div className="advanced-option-item-right-content">
<div
className={styles.advancedOptionItemInput}
className="advanced-option-item-input"
style={{ display: showInput ? 'block' : 'none' }}
>
{input}

View File

@@ -1,150 +0,0 @@
.advancedOptionItem {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: var(--spacing-8);
border-bottom: 1px solid var(--l1-border);
}
.advancedOptionItemLeftContent {
display: flex;
flex-direction: column;
gap: var(--spacing-3);
}
.advancedOptionItemTitle {
color: var(--l2-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}
.advancedOptionItemDescription {
color: var(--muted-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 400;
}
.advancedOptionItemInput {
margin-top: var(--spacing-8);
:global(.ant-input) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
:global(.ant-select) {
:global(.ant-select-selector) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
:global(.ant-select-selection-placeholder) {
font-family: 'Space Mono';
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--l2-foreground);
}
}
}
.advancedOptionItemRightContent {
display: flex;
align-items: flex-start;
gap: var(--spacing-8);
}
.advancedOptionItemInputGroup {
display: flex;
align-items: center;
gap: var(--spacing-4);
:global(.ant-input) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
:global(.ant-select) {
:global(.ant-select-selector) {
background-color: var(--l2-background);
color: var(--l1-foreground);
height: 32px;
border: 1px solid var(--l1-border);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
:global(.ant-select-selection-placeholder) {
font-family: 'Space Mono';
}
}
:global(.ant-select-selection-item) {
color: var(--l1-foreground);
}
:global(.ant-select-arrow) {
color: var(--l2-foreground);
}
}
}
.advancedOptionItemButton {
display: flex;
align-items: center;
gap: var(--spacing-4);
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
border-radius: 4px;
}

View File

@@ -0,0 +1,150 @@
.advanced-option-item {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 16px;
border-bottom: 1px solid var(--l1-border);
.advanced-option-item-left-content {
display: flex;
flex-direction: column;
gap: 6px;
.advanced-option-item-title {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
}
.advanced-option-item-description {
color: var(--muted-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 400;
}
.advanced-option-item-input {
margin-top: 16px;
.ant-input {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&::placeholder {
font-family: 'Space Mono';
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select {
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
.ant-select-selection-placeholder {
font-family: 'Space Mono';
}
}
.ant-select-selection-item {
color: var(--l1-foreground);
}
.ant-select-arrow {
color: var(--l2-foreground);
}
}
}
}
.advanced-option-item-right-content {
display: flex;
align-items: flex-start;
gap: 16px;
.advanced-option-item-input-group {
display: flex;
align-items: center;
gap: 8px;
.ant-input {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
.ant-select {
.ant-select-selector {
background-color: var(--l2-background);
color: var(--l1-foreground);
height: 32px;
border: 1px solid var(--l1-border);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
.ant-select-selection-placeholder {
font-family: 'Space Mono';
}
}
.ant-select-selection-item {
color: var(--l1-foreground);
}
.ant-select-arrow {
color: var(--l2-foreground);
}
}
}
.advanced-option-item-button {
display: flex;
align-items: center;
gap: 8px;
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
border-radius: 4px;
}
}
}

View File

@@ -4,15 +4,13 @@ import { Typography } from '@signozhq/ui/typography';
import { useCreateAlertState } from '../context';
import AdvancedOptionItem from './AdvancedOptionItem';
import advancedOptionStyles from './AdvancedOptionItem/styles.module.scss';
import EvaluationCadence from './EvaluationCadence';
import styles from './styles.module.scss';
function AdvancedOptions(): JSX.Element {
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
return (
<div className={styles.advancedOptionsContainer}>
<div className="advanced-options-container">
<Collapse bordered={false}>
<Collapse.Panel header="ADVANCED OPTIONS" key="1">
<EvaluationCadence />
@@ -21,7 +19,7 @@ function AdvancedOptions(): JSX.Element {
description="Send notification if no data is received for a specified time period."
tooltipText="Useful for monitoring data pipelines or services that should continuously send data. For example, alert if no logs are received for 10 minutes"
input={
<div className={advancedOptionStyles.advancedOptionItemInputGroup}>
<div className="advanced-option-item-input-group">
<Input
placeholder="Enter tolerance limit..."
type="number"
@@ -54,7 +52,7 @@ function AdvancedOptions(): JSX.Element {
description="Only trigger alert when there are enough data points to make a reliable decision."
tooltipText="Prevents false alarms when there's insufficient data. For example, require at least 5 data points before checking if CPU usage is above 80%."
input={
<div className={advancedOptionStyles.advancedOptionItemInputGroup}>
<div className="advanced-option-item-input-group">
<Input
placeholder="Enter minimum datapoints..."
style={{ width: 100 }}

View File

@@ -6,8 +6,6 @@ import { INITIAL_ADVANCED_OPTIONS_STATE } from 'container/CreateAlertV2/context/
import { IEditCustomScheduleProps } from 'container/CreateAlertV2/EvaluationSettings/types';
import { Calendar1, Pencil, Trash } from '@signozhq/icons';
import styles from './styles.module.scss';
function EditCustomSchedule({
setIsEvaluationCadenceDetailsVisible,
setIsPreviewVisible,
@@ -19,7 +17,7 @@ function EditCustomSchedule({
return (
<Typography.Text>
<Typography.Text>Every</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.custom.repeatEvery
.charAt(0)
.toUpperCase() +
@@ -28,7 +26,7 @@ function EditCustomSchedule({
{advancedOptions.evaluationCadence.custom.repeatEvery !== 'day' && (
<>
<Typography.Text>on</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.custom.occurence
.map(
(occurence) => occurence.charAt(0).toUpperCase() + occurence.slice(1),
@@ -38,7 +36,7 @@ function EditCustomSchedule({
</>
)}
<Typography.Text>at</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.custom.startAt}
</Typography.Text>
</Typography.Text>
@@ -47,11 +45,11 @@ function EditCustomSchedule({
return (
<Typography.Text>
<Typography.Text>Starting on</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.rrule.date?.format('DD/MM/YYYY')}
</Typography.Text>
<Typography.Text>at</Typography.Text>
<Typography.Text className={styles.highlight}>
<Typography.Text className="highlight">
{advancedOptions.evaluationCadence.rrule.startAt}
</Typography.Text>
</Typography.Text>
@@ -79,9 +77,9 @@ function EditCustomSchedule({
};
return (
<div className={styles.editCustomSchedule} data-testid="edit-custom-schedule">
<div className="edit-custom-schedule">
{displayText}
<div>
<div className="button-row">
<Button.Group>
<Button type="default" onClick={handleEdit}>
<Pencil size={12} />

View File

@@ -8,8 +8,9 @@ import { ADVANCED_OPTIONS_TIME_UNIT_OPTIONS } from '../../context/constants';
import EditCustomSchedule from './EditCustomSchedule';
import EvaluationCadenceDetails from './EvaluationCadenceDetails';
import EvaluationCadencePreview from './EvaluationCadencePreview';
import advancedOptionStyles from '../AdvancedOptionItem/styles.module.scss';
import styles from './styles.module.scss';
import './styles.scss';
import '../AdvancedOptionItem/styles.scss';
function EvaluationCadence(): JSX.Element {
const { advancedOptions, setAdvancedOptions } = useCreateAlertState();
@@ -40,31 +41,25 @@ function EvaluationCadence(): JSX.Element {
// };
return (
<div className={styles.evaluationCadenceContainer}>
<div
className={`${advancedOptionStyles.advancedOptionItem} ${styles.evaluationCadenceItem}`}
>
<div className={advancedOptionStyles.advancedOptionItemLeftContent}>
<Typography.Text className={advancedOptionStyles.advancedOptionItemTitle}>
<div className="evaluation-cadence-container">
<div className="advanced-option-item evaluation-cadence-item">
<div className="advanced-option-item-left-content">
<Typography.Text className="advanced-option-item-title">
How often to check
<Tooltip title="Controls how frequently the alert evaluates your conditions. For most alerts, 1-5 minutes is sufficient.">
<Info data-testid="evaluation-cadence-tooltip-icon" size={16} />
</Tooltip>
</Typography.Text>
<Typography.Text
className={advancedOptionStyles.advancedOptionItemDescription}
>
<Typography.Text className="advanced-option-item-description">
How frequently this alert checks your data. Default: Every 1 minute
</Typography.Text>
</div>
{isCustomScheduleButtonVisible && (
<div
className={advancedOptionStyles.advancedOptionItemRightContent}
className="advanced-option-item-right-content"
data-testid="evaluation-cadence-input-group"
>
<Input.Group
className={advancedOptionStyles.advancedOptionItemInputGroup}
>
<Input.Group className="advanced-option-item-input-group">
<Input
type="number"
placeholder="Enter time"

View File

@@ -21,7 +21,6 @@ import {
isValidRRule,
} from '../utils';
import { ScheduleList } from './EvaluationCadencePreview';
import styles from './styles.module.scss';
function EvaluationCadenceDetails({
setIsOpen,
@@ -91,8 +90,8 @@ function EvaluationCadenceDetails({
}, [evaluationCadence.custom.repeatEvery]);
const EditorView = (
<div className={styles.editorView} data-testid="editor-view">
<div className={styles.selectGroup}>
<div className="editor-view" data-testid="editor-view">
<div className="select-group">
<Typography.Text>REPEAT EVERY</Typography.Text>
<Select
options={EVALUATION_CADENCE_REPEAT_EVERY_OPTIONS}
@@ -114,7 +113,7 @@ function EvaluationCadenceDetails({
/>
</div>
{evaluationCadence.custom.repeatEvery !== 'day' && (
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>ON DAY(S)</Typography.Text>
<Select
options={occurenceOptions}
@@ -136,7 +135,7 @@ function EvaluationCadenceDetails({
/>
</div>
)}
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>AT</Typography.Text>
<TimeInput
value={evaluationCadence.custom.startAt}
@@ -151,7 +150,7 @@ function EvaluationCadenceDetails({
}
/>
</div>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
@@ -175,8 +174,8 @@ function EvaluationCadenceDetails({
);
const RRuleView = (
<div className={styles.rruleView} data-testid="rrule-view">
<div className={styles.selectGroup}>
<div className="rrule-view" data-testid="rrule-view">
<div className="select-group">
<Typography.Text>STARTING ON</Typography.Text>
<DatePicker
value={evaluationCadence.rrule.date}
@@ -192,7 +191,7 @@ function EvaluationCadenceDetails({
placeholder="Select date"
/>
</div>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>AT</Typography.Text>
<TimeInput
value={evaluationCadence.rrule.startAt}
@@ -295,19 +294,19 @@ function EvaluationCadenceDetails({
};
return (
<div className={styles.evaluationCadenceDetails}>
<Typography.Text className={styles.evaluationCadenceDetailsTitle}>
<div className="evaluation-cadence-details">
<Typography.Text className="evaluation-cadence-details-title">
Add Custom Schedule
</Typography.Text>
<div className={styles.evaluationCadenceDetailsContent}>
<div className={styles.evaluationCadenceDetailsContentRow}>
<div className={styles.querySectionTabs}>
<div className={styles.querySectionQueryActions}>
<div className="evaluation-cadence-details-content">
<div className="evaluation-cadence-details-content-row">
<div className="query-section-tabs">
<div className="query-section-query-actions">
{tabs.map((tab) => (
<Button
key={tab.value}
className={classNames(styles.explorerViewOption, {
[styles.activeTab]: activeTab === tab.value,
className={classNames('list-view-tab', 'explorer-view-option', {
'active-tab': activeTab === tab.value,
})}
onClick={(): void => {
handleChangeTab(tab.value as 'editor' | 'rrule');
@@ -321,7 +320,7 @@ function EvaluationCadenceDetails({
</div>
{activeTab === 'editor' && EditorView}
{activeTab === 'rrule' && RRuleView}
<div className={styles.buttonsRow}>
<div className="buttons-row">
<Button type="default" onClick={handleDiscard}>
Discard
</Button>
@@ -334,7 +333,7 @@ function EvaluationCadenceDetails({
</Button>
</div>
</div>
<div className={styles.evaluationCadenceDetailsContentRow}>
<div className="evaluation-cadence-details-content-row">
<ScheduleList
schedule={schedule}
currentTimezone={evaluationCadence.custom.timezone}

View File

@@ -10,7 +10,6 @@ import {
buildAlertScheduleFromCustomSchedule,
buildAlertScheduleFromRRule,
} from '../utils';
import styles from './styles.module.scss';
export function ScheduleList({
schedule,
@@ -18,21 +17,21 @@ export function ScheduleList({
}: IScheduleListProps): JSX.Element {
if (schedule && schedule.length > 0) {
return (
<div className={styles.schedulePreview} data-testid="schedule-preview">
<div className={styles.schedulePreviewHeader}>
<div className="schedule-preview" data-testid="schedule-preview">
<div className="schedule-preview-header">
<Calendar size={16} />
<Typography.Text className={styles.schedulePreviewTitle}>
<Typography.Text className="schedule-preview-title">
Schedule Preview
</Typography.Text>
</div>
<div className={styles.schedulePreviewList}>
<div className="schedule-preview-list">
{schedule.map((date) => (
<div key={date.toISOString()} className={styles.schedulePreviewItem}>
<div className={styles.schedulePreviewTimeline}>
<div className={styles.schedulePreviewTimelineLine} />
<div key={date.toISOString()} className="schedule-preview-item">
<div className="schedule-preview-timeline">
<div className="schedule-preview-timeline-line" />
</div>
<div className={styles.schedulePreviewContent}>
<div className={styles.schedulePreviewDate}>
<div className="schedule-preview-content">
<div className="schedule-preview-date">
{date.toLocaleDateString('en-US', {
weekday: 'short',
month: 'short',
@@ -46,8 +45,8 @@ export function ScheduleList({
second: '2-digit',
})}
</div>
<div className={styles.schedulePreviewSeparator} />
<div className={styles.schedulePreviewTimezone}>
<div className="schedule-preview-separator" />
<div className="schedule-preview-timezone">
{
TIMEZONE_DATA.find((timezone) => timezone.value === currentTimezone)
?.label
@@ -62,7 +61,7 @@ export function ScheduleList({
}
return (
<div className={styles.noSchedule} data-testid="no-schedule">
<div className="no-schedule" data-testid="no-schedule">
<Info size={32} />
<Typography.Text>
Please fill the relevant information to generate a schedule
@@ -99,19 +98,13 @@ function EvaluationCadencePreview({
open={isOpen}
onCancel={(): void => setIsOpen(false)}
footer={null}
className={styles.evaluationCadencePreviewModal}
className="evaluation-cadence-preview-modal"
width={800}
centered
>
<div
className={`${styles.evaluationCadenceDetails} ${styles.evaluationCadencePreview}`}
>
<div
className={`${styles.evaluationCadenceDetailsContent} ${styles.evaluationCadencePreviewContent}`}
>
<div
className={`${styles.evaluationCadenceDetailsContentRow} ${styles.evaluationCadencePreviewContentRow}`}
>
<div className="evaluation-cadence-details evaluation-cadence-preview">
<div className="evaluation-cadence-details-content">
<div className="evaluation-cadence-details-content-row">
<ScheduleList
schedule={schedule}
currentTimezone={advancedOptions.evaluationCadence.custom.timezone}

View File

@@ -1,3 +1,5 @@
import EvaluationCadence from './EvaluationCadence';
import './styles.scss';
export default EvaluationCadence;

View File

@@ -1,450 +0,0 @@
.evaluationCadenceContainer {
border-bottom: 1px solid var(--l1-border);
}
.evaluationCadenceItem {
border-bottom: none !important;
}
.editCustomSchedule {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--spacing-8);
padding: var(--spacing-8);
:global(.ant-typography) {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
}
:global(.ant-btn-group) {
:global(.ant-btn) {
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
display: flex;
align-items: center;
gap: var(--spacing-4);
}
}
}
.highlight {
background-color: var(--l1-background);
padding: var(--spacing-2) var(--spacing-4);
border-radius: 4px;
color: var(--l2-foreground);
font-weight: var(--font-weight-medium);
margin: 0 var(--spacing-2);
font-size: var(--periscope-font-size-base);
}
.evaluationCadenceDetails {
margin: var(--spacing-8);
display: flex;
flex-direction: column;
gap: var(--spacing-8);
border: 1px solid var(--l1-border);
}
.evaluationCadenceDetailsTitle {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
padding-left: var(--spacing-8);
padding-top: var(--spacing-8);
}
.querySectionTabs {
display: flex;
align-items: center;
}
.querySectionQueryActions {
display: flex;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
flex-direction: row;
border-bottom: none;
margin-bottom: -1px;
}
.explorerViewOption {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
border: none;
padding: 9px;
box-shadow: none;
border-radius: 0;
border-left: 0.5px solid var(--l1-border);
border-bottom: 0.5px solid var(--l1-border);
width: 120px;
height: 36px;
gap: var(--spacing-4);
&:first-child {
border-left: 1px solid transparent;
}
&:hover {
background-color: transparent !important;
border-left: 1px solid transparent !important;
color: var(--l1-foreground);
}
&:disabled {
background-color: var(--l2-background);
opacity: 0.6;
}
}
.activeTab {
background-color: var(--l1-background);
border-bottom: none;
&:hover {
background-color: var(--l1-background) !important;
}
}
.evaluationCadenceDetailsContent {
display: flex;
gap: var(--spacing-8);
border-top: 1px solid var(--l1-border);
padding: var(--spacing-8);
}
.evaluationCadenceDetailsContentRow {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
flex: 1;
height: 500px;
overflow-y: scroll;
padding-right: var(--spacing-8);
}
.editorView {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
}
.rruleView {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
textarea {
height: 200px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: 'Space Mono';
font-size: var(--periscope-font-size-base);
&::placeholder {
font-family: 'Space Mono';
color: var(--muted-foreground) !important;
}
}
}
.selectGroup {
display: flex;
flex-direction: column;
gap: var(--spacing-2);
:global(.ant-typography) {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
:global(.ant-select) {
border: 1px solid var(--l1-border);
:global(.ant-select-selector) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
:global(.ant-picker) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
:global(.ant-picker-input) {
background-color: var(--l2-background);
color: var(--l1-foreground);
}
}
}
.buttonsRow {
display: flex;
align-items: center;
gap: var(--spacing-8);
margin-top: var(--spacing-8);
}
.noSchedule {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: var(--spacing-4);
height: 100%;
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
}
.schedulePreview {
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
min-height: 0;
}
.schedulePreviewHeader {
display: flex;
align-items: center;
gap: var(--spacing-4);
padding: var(--spacing-4) 0;
background-color: var(--card);
position: sticky;
top: 0;
z-index: 1;
border-bottom: 1px solid var(--l1-border);
}
.schedulePreviewTitle {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
.schedulePreviewList {
display: flex;
flex-direction: column;
gap: 0;
flex: 1;
overflow-y: auto;
padding-top: var(--spacing-4);
&::-webkit-scrollbar {
width: 0.1rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l1-border);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
}
.schedulePreviewItem {
display: flex;
align-items: center;
gap: var(--spacing-6);
padding: var(--spacing-4) 0;
}
.schedulePreviewTimeline {
display: flex;
flex-direction: column;
align-items: center;
min-width: 20px;
}
.schedulePreviewTimelineLine {
width: 1px;
height: 20px;
background-color: var(--l2-background);
}
.schedulePreviewContent {
display: flex;
align-items: center;
gap: var(--spacing-6);
flex: 1;
}
.schedulePreviewDate {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
font-weight: 400;
white-space: nowrap;
}
.schedulePreviewSeparator {
flex: 1;
height: 1px;
border-top: 1px dashed var(--l1-border);
}
.schedulePreviewTimezone {
color: var(--muted-foreground);
font-size: 12px;
font-weight: 400;
white-space: nowrap;
}
/* Global styles for ant-picker date panel */
:global(.ant-picker-date-panel) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
:global(.ant-picker-date-panel-layout) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
:global(.ant-picker-date-panel-header) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
/* Custom modal styles for preview */
.evaluationCadencePreviewModal {
:global(.ant-modal-content) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: var(--spacing-4);
}
:global(.ant-modal-header) {
background-color: var(--l2-background);
border-bottom: 1px solid var(--l1-border);
padding: var(--spacing-8) 20px;
:global(.ant-modal-title) {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-medium);
font-weight: var(--font-weight-semibold);
}
}
:global(.ant-modal-close) {
color: var(--l2-foreground);
top: var(--spacing-8);
right: 20px;
&:hover {
color: var(--l1-foreground);
}
}
:global(.ant-modal-body) {
padding: 0;
background-color: var(--l2-background);
}
}
.evaluationCadencePreview {
border: none;
margin: 0;
}
.evaluationCadencePreviewContent {
border-top: none;
padding: 0;
}
.evaluationCadencePreviewContentRow {
height: auto;
max-height: 60vh;
overflow-y: auto;
padding: var(--spacing-6);
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l2-background);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
}
.previewScheduleHeader {
background-color: var(--card);
border-bottom: 1px solid var(--l1-border);
padding: var(--spacing-6) var(--spacing-8);
margin: calc(-1 * var(--spacing-6)) calc(-1 * var(--spacing-6))
var(--spacing-8) calc(-1 * var(--spacing-6));
}
.previewScheduleTitle {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
.previewScheduleItem {
padding: var(--spacing-6) 0;
border-bottom: 1px solid var(--l1-border);
&:last-child {
border-bottom: none;
}
}
.previewScheduleTimelineLine {
width: 2px;
height: 24px;
background-color: var(--primary-background);
border-radius: 1px;
}
.previewScheduleDate {
color: var(--l1-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
.previewScheduleTimezone {
background-color: var(--l1-background);
padding: var(--spacing-2) var(--spacing-4);
border-radius: 4px;
font-size: 12px;
}
.previewNoSchedule {
min-height: 300px;
padding: 40px var(--spacing-6);
svg {
color: var(--muted-foreground);
}
}

View File

@@ -0,0 +1,453 @@
.evaluation-cadence-container {
border-bottom: 1px solid var(--l1-border);
.evaluation-cadence-item {
border-bottom: none !important;
}
.edit-custom-schedule {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 16px;
.ant-typography {
color: var(--l1-foreground);
font-size: 13px;
.highlight {
background-color: var(--l1-background);
padding: 4px 8px;
border-radius: 4px;
color: var(--l2-foreground);
font-weight: 500;
margin: 0 4px;
font-size: 13px;
}
}
.ant-btn-group {
.ant-btn {
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: 13px;
display: flex;
align-items: center;
gap: 8px;
}
}
}
}
.evaluation-cadence-details {
margin: 16px;
display: flex;
flex-direction: column;
gap: 16px;
border: 1px solid var(--l1-border);
.evaluation-cadence-details-title {
color: var(--l1-foreground);
font-size: 13px;
font-weight: 500;
padding-left: 16px;
padding-top: 16px;
}
.query-section-tabs {
display: flex;
align-items: center;
.query-section-query-actions {
display: flex;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
flex-direction: row;
border-bottom: none;
margin-bottom: -1px;
.explorer-view-option {
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
border: none;
padding: 9px;
box-shadow: none;
border-radius: 0px;
border-left: 0.5px solid var(--l1-border);
border-bottom: 0.5px solid var(--l1-border);
width: 120px;
height: 36px;
gap: 8px;
&.active-tab {
background-color: var(--l1-background);
border-bottom: none;
&:hover {
background-color: var(--l1-background) !important;
}
}
&:disabled {
background-color: var(--l2-background);
opacity: 0.6;
}
&:first-child {
border-left: 1px solid transparent;
}
&:hover {
background-color: transparent !important;
border-left: 1px solid transparent !important;
color: var(--l1-foreground);
}
}
}
}
.evaluation-cadence-details-content {
display: flex;
gap: 16px;
border-top: 1px solid var(--l1-border);
padding: 16px;
.evaluation-cadence-details-content-row {
display: flex;
flex-direction: column;
gap: 16px;
flex: 1;
height: 500px;
overflow-y: scroll;
padding-right: 16px;
.editor-view,
.rrule-view {
display: flex;
flex-direction: column;
gap: 16px;
textarea {
height: 200px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: 'Space Mono';
font-size: 13px;
&::placeholder {
font-family: 'Space Mono';
color: var(--muted-foreground) !important;
}
}
.select-group {
display: flex;
flex-direction: column;
gap: 4px;
.ant-typography {
color: var(--l1-foreground);
font-size: 13px;
font-weight: 500;
}
.ant-select {
border: 1px solid var(--l1-border);
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
.ant-picker {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
.ant-picker-input {
background-color: var(--l2-background);
color: var(--l1-foreground);
}
}
}
}
.buttons-row {
display: flex;
align-items: center;
gap: 16px;
margin-top: 16px;
}
.no-schedule {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 8px;
height: 100%;
color: var(--l1-foreground);
font-size: 13px;
}
.schedule-preview {
display: flex;
flex-direction: column;
width: 100%;
flex: 1;
min-height: 0;
.schedule-preview-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
background-color: var(--card);
position: sticky;
top: 0;
z-index: 1;
border-bottom: 1px solid var(--l1-border);
.schedule-preview-title {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 500;
}
}
.schedule-preview-list {
display: flex;
flex-direction: column;
gap: 0;
flex: 1;
overflow-y: auto;
padding-top: 8px;
&::-webkit-scrollbar {
width: 0.1rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l1-border);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
.schedule-preview-item {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
.schedule-preview-timeline {
display: flex;
flex-direction: column;
align-items: center;
min-width: 20px;
.schedule-preview-timeline-line {
width: 1px;
height: 20px;
background-color: var(--l2-background);
}
}
.schedule-preview-content {
display: flex;
align-items: center;
gap: 12px;
flex: 1;
.schedule-preview-date {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 400;
white-space: nowrap;
}
.schedule-preview-separator {
flex: 1;
height: 1px;
border-top: 1px dashed var(--l1-border);
}
.schedule-preview-timezone {
color: var(--muted-foreground);
font-size: 12px;
font-weight: 400;
white-space: nowrap;
}
}
}
}
}
}
}
}
.ant-picker-date-panel {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
.ant-picker-date-panel-layout {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
.ant-picker-date-panel-header {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
// Custom modal styles for preview
.evaluation-cadence-preview-modal {
.ant-modal-content {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 8px;
}
.ant-modal-header {
background-color: var(--l2-background);
border-bottom: 1px solid var(--l1-border);
padding: 16px 20px;
.ant-modal-title {
color: var(--l1-foreground);
font-size: 16px;
font-weight: 600;
}
}
.ant-modal-close {
color: var(--l2-foreground);
top: 16px;
right: 20px;
&:hover {
color: var(--l1-foreground);
}
}
.ant-modal-body {
padding: 0;
background-color: var(--l2-background);
}
.evaluation-cadence-details {
border: none;
margin: 0;
.evaluation-cadence-details-content {
border-top: none;
padding: 0;
.evaluation-cadence-details-content-row {
height: auto;
max-height: 60vh;
overflow-y: auto;
padding: 12px;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l2-background);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
.schedule-preview {
.schedule-preview-header {
background-color: var(--card);
border-bottom: 1px solid var(--l1-border);
padding: 12px 16px;
margin: -12px -12px 16px -12px;
.schedule-preview-title {
color: var(--l1-foreground);
font-size: 13px;
font-weight: 500;
}
}
.schedule-preview-list {
.schedule-preview-item {
padding: 12px 0;
border-bottom: 1px solid var(--l1-border);
&:last-child {
border-bottom: none;
}
.schedule-preview-timeline {
.schedule-preview-timeline-line {
width: 2px;
height: 24px;
background-color: var(--primary-background);
border-radius: 1px;
}
}
.schedule-preview-content {
.schedule-preview-date {
color: var(--l1-foreground);
font-size: 13px;
font-weight: 500;
}
.schedule-preview-timezone {
background-color: var(--l1-background);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
}
}
}
}
.no-schedule {
min-height: 300px;
padding: 40px 12px;
svg {
color: var(--muted-foreground);
}
}
}
}
}
}
// Light mode styles

View File

@@ -5,7 +5,8 @@ import { ChevronDown, ChevronUp } from '@signozhq/icons';
import { useCreateAlertState } from '../context';
import EvaluationWindowPopover from './EvaluationWindowPopover';
import { getEvaluationWindowTypeText, getTimeframeText } from './utils';
import styles from './styles.module.scss';
import './styles.scss';
function EvaluationSettings(): JSX.Element {
const { evaluationWindow, setEvaluationWindow } = useCreateAlertState();
@@ -27,14 +28,13 @@ function EvaluationSettings(): JSX.Element {
}
trigger="click"
showArrow={false}
rootClassName="evaluation-window-popover-overlay"
>
<Button data-testid="evaluation-settings-button">
<div className={styles.evaluateAlertConditionsButtonLeft}>
<div className="evaluate-alert-conditions-button-left">
{getTimeframeText(evaluationWindow)}
</div>
<div className={styles.evaluateAlertConditionsButtonRight}>
<div className={styles.evaluateAlertConditionsButtonRightText}>
<div className="evaluate-alert-conditions-button-right">
<div className="evaluate-alert-conditions-button-right-text">
{getEvaluationWindowTypeText(evaluationWindow.windowType)}
</div>
{isEvaluationWindowPopoverOpen ? (
@@ -49,7 +49,7 @@ function EvaluationSettings(): JSX.Element {
return (
<div
className={styles.condensedEvaluationSettingsContainer}
className="condensed-evaluation-settings-container"
data-testid="condensed-evaluation-settings-container"
>
{popoverContent}

View File

@@ -12,7 +12,6 @@ import {
import TimeInput from '../TimeInput';
import { IEvaluationWindowDetailsProps } from '../types';
import { getCumulativeWindowTimeframeText } from '../utils';
import styles from '../styles.module.scss';
function EvaluationWindowDetails({
evaluationWindow,
@@ -118,12 +117,12 @@ function EvaluationWindowDetails({
if (isCurrentHour) {
return (
<div className={styles.evaluationWindowDetails}>
<div className="evaluation-window-details">
<Typography.Text>
{getCumulativeWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Typography.Text>{displayText}</Typography.Text>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>STARTING AT MINUTE</Typography.Text>
<Select
options={currentHourOptions}
@@ -139,19 +138,19 @@ function EvaluationWindowDetails({
if (isCurrentDay) {
return (
<div className={styles.evaluationWindowDetails}>
<div className="evaluation-window-details">
<Typography.Text>
{getCumulativeWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Typography.Text>{displayText}</Typography.Text>
<div className={`${styles.selectGroup} ${styles.timeSelectGroup}`}>
<div className="select-group time-select-group">
<Typography.Text>STARTING AT</Typography.Text>
<TimeInput
value={evaluationWindow.startingAt.time}
onChange={handleTimeChange}
/>
</div>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>SELECT TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
@@ -167,12 +166,12 @@ function EvaluationWindowDetails({
if (isCurrentMonth) {
return (
<div className={styles.evaluationWindowDetails}>
<div className="evaluation-window-details">
<Typography.Text>
{getCumulativeWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Typography.Text>{displayText}</Typography.Text>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>STARTING ON DAY</Typography.Text>
<Select
options={currentMonthOptions}
@@ -182,14 +181,14 @@ function EvaluationWindowDetails({
data-testid="evaluation-window-details-starting-at-select"
/>
</div>
<div className={`${styles.selectGroup} ${styles.timeSelectGroup}`}>
<div className="select-group time-select-group">
<Typography.Text>STARTING AT</Typography.Text>
<TimeInput
value={evaluationWindow.startingAt.time}
onChange={handleTimeChange}
/>
</div>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>SELECT TIMEZONE</Typography.Text>
<Select
options={TIMEZONE_DATA}
@@ -204,13 +203,13 @@ function EvaluationWindowDetails({
}
return (
<div className={styles.evaluationWindowDetails}>
<div className="evaluation-window-details">
<Typography.Text>
{getRollingWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
<Typography.Text>Specify custom duration</Typography.Text>
<Typography.Text>{displayText}</Typography.Text>
<div className={styles.selectGroup}>
<div className="select-group">
<Typography.Text>VALUE</Typography.Text>
<Input
name="value"
@@ -221,7 +220,7 @@ function EvaluationWindowDetails({
data-testid="evaluation-window-details-custom-rolling-window-duration-input"
/>
</div>
<div className={`${styles.selectGroup} ${styles.timeSelectGroup}`}>
<div className="select-group time-select-group">
<Typography.Text>UNIT</Typography.Text>
<Select
options={ADVANCED_OPTIONS_TIME_UNIT_OPTIONS}

View File

@@ -16,7 +16,6 @@ import {
} from '../types';
import EvaluationWindowDetails from './EvaluationWindowDetails';
import { useKeyboardNavigationForEvaluationWindowPopover } from './useKeyboardNavigation';
import styles from '../styles.module.scss';
function EvaluationWindowPopover({
evaluationWindow,
@@ -52,42 +51,34 @@ function EvaluationWindowPopover({
onChange: (value: string) => void,
sectionId: string,
): JSX.Element => (
<div
className={styles.evaluationWindowContentItem}
data-section-id={sectionId}
>
<Typography.Text className={styles.evaluationWindowContentItemLabel}>
<div className="evaluation-window-content-item" data-section-id={sectionId}>
<Typography.Text className="evaluation-window-content-item-label">
{label}
</Typography.Text>
<div className={styles.evaluationWindowContentList}>
{contentOptions.map((option, index) => {
const isActive = currentValue === option.value;
return (
<div
className={classNames(styles.evaluationWindowContentListItem, {
[styles.evaluationWindowContentListItemActive]: isActive,
})}
key={option.value}
role="button"
tabIndex={0}
data-value={option.value}
data-section-id={sectionId}
data-testid={`${sectionId}-option-${option.value}`}
data-active={isActive}
onClick={(): void => onChange(option.value)}
onKeyDown={(e): void => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onChange(option.value);
}
}}
ref={index === 0 ? firstItemRef : undefined}
>
<Typography.Text>{option.label}</Typography.Text>
{isActive && <Check size={12} />}
</div>
);
})}
<div className="evaluation-window-content-list">
{contentOptions.map((option, index) => (
<div
className={classNames('evaluation-window-content-list-item', {
active: currentValue === option.value,
})}
key={option.value}
role="button"
tabIndex={0}
data-value={option.value}
data-section-id={sectionId}
onClick={(): void => onChange(option.value)}
onKeyDown={(e): void => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
onChange(option.value);
}
}}
ref={index === 0 ? firstItemRef : undefined}
>
<Typography.Text>{option.label}</Typography.Text>
{currentValue === option.value && <Check size={12} />}
</div>
))}
</div>
</div>
);
@@ -103,7 +94,7 @@ function EvaluationWindowPopover({
);
}
return (
<div className={styles.selectionContent}>
<div className="selection-content">
<Typography.Text>
{getRollingWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
@@ -117,7 +108,7 @@ function EvaluationWindowPopover({
!evaluationWindow.timeframe
) {
return (
<div className={styles.selectionContent}>
<div className="selection-content">
<Typography.Text>
{getCumulativeWindowDescription(evaluationWindow.timeframe)}
</Typography.Text>
@@ -136,12 +127,12 @@ function EvaluationWindowPopover({
return (
<div
className={styles.evaluationWindowPopover}
className="evaluation-window-popover"
ref={containerRef}
role="menu"
aria-label="Evaluation window options"
>
<div className={styles.evaluationWindowContent}>
<div className="evaluation-window-content">
{renderEvaluationWindowContent(
'EVALUATION WINDOW',
EVALUATION_WINDOW_TYPE,

View File

@@ -1,54 +0,0 @@
.timeInputContainer {
display: flex;
align-items: center;
gap: 0;
}
// Compound + descendant selector keeps specificity above
// parent `.selectGroup :global(.ant-input)` override so the
// 40px field width is not clobbered to 60%.
.timeInputContainer :global(.ant-input).timeInputField {
width: 40px;
height: 32px;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
font-family: 'Space Mono', monospace;
font-size: 13px;
font-weight: 600;
text-align: center;
border-radius: 4px;
&::placeholder {
color: var(--l2-foreground);
font-family: 'Space Mono', monospace;
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
outline: none;
}
&:disabled {
background-color: var(--l2-background);
color: var(--l2-foreground);
cursor: not-allowed;
&:hover {
border-color: var(--l1-border);
}
}
}
.timeInputSeparator {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 600;
margin: 0 4px;
user-select: none;
}

View File

@@ -0,0 +1,51 @@
.time-input-container {
display: flex;
align-items: center;
gap: 0;
.time-input-field {
width: 40px;
height: 32px;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
font-family: 'Space Mono', monospace;
font-size: 13px;
font-weight: 600;
text-align: center;
border-radius: 4px;
&::placeholder {
color: var(--l2-foreground);
font-family: 'Space Mono', monospace;
}
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
outline: none;
}
&:disabled {
background-color: var(--l2-background);
color: var(--l2-foreground);
cursor: not-allowed;
&:hover {
border-color: var(--l1-border);
}
}
}
.time-input-separator {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 600;
margin: 0 4px;
user-select: none;
}
}

View File

@@ -1,7 +1,6 @@
import { Input } from '@signozhq/ui/input';
import React, { useEffect, useState } from 'react';
import styles from './TimeInput.module.scss';
import { Input } from '@signozhq/ui/input';
import './TimeInput.scss';
export interface TimeInputProps {
value?: string; // Format: "HH:MM:SS"
@@ -145,10 +144,7 @@ function TimeInput({
};
return (
<div
data-testid="time-input"
className={`${styles.timeInputContainer} ${className}`.trim()}
>
<div data-testid="time-input" className={`time-input-container ${className}`}>
<Input
data-field="hours"
value={hours}
@@ -157,11 +153,11 @@ function TimeInput({
onKeyDown={(e): void => handleKeyDown(e, 'hours')}
disabled={disabled}
maxLength={2}
className={styles.timeInputField}
className="time-input-field"
placeholder="00"
data-testid="time-input-hours"
/>
<span className={styles.timeInputSeparator}>:</span>
<span className="time-input-separator">:</span>
<Input
data-field="minutes"
value={minutes}
@@ -170,11 +166,11 @@ function TimeInput({
onKeyDown={(e): void => handleKeyDown(e, 'minutes')}
disabled={disabled}
maxLength={2}
className={styles.timeInputField}
className="time-input-field"
placeholder="00"
data-testid="time-input-minutes"
/>
<span className={styles.timeInputSeparator}>:</span>
<span className="time-input-separator">:</span>
<Input
data-field="seconds"
value={seconds}
@@ -183,7 +179,7 @@ function TimeInput({
onKeyDown={(e): void => handleKeyDown(e, 'seconds')}
disabled={disabled}
maxLength={2}
className={styles.timeInputField}
className="time-input-field"
placeholder="00"
data-testid="time-input-seconds"
/>

View File

@@ -13,12 +13,9 @@ jest.spyOn(alertState, 'useCreateAlertState').mockReturnValue(
const ALERT_WHEN_DATA_STOPS_COMING_TEXT = 'Alert when data stops coming';
const MINIMUM_DATA_REQUIRED_TEXT = 'Minimum data required';
// const ACCOUNT_FOR_DATA_DELAY_TEXT = 'Account for data delay';
const ACCOUNT_FOR_DATA_DELAY_TEXT = 'Account for data delay';
const ADVANCED_OPTION_ITEM_CLASS = '.advanced-option-item';
const SWITCH_ROLE_SELECTOR = '[role="switch"]';
const SEND_NOTIFICATION_TEST_ID =
'send-notification-if-data-is-missing-container';
const ENFORCE_MINIMUM_DATAPOINTS_TEST_ID =
'enforce-minimum-datapoints-container';
describe('AdvancedOptions', () => {
it('should render evaluation cadence and the advanced options minimized by default', () => {
@@ -67,9 +64,9 @@ describe('AdvancedOptions', () => {
const collapse = screen.getByRole('button', { name: /ADVANCED OPTIONS/i });
fireEvent.click(collapse);
const alertWhenDataStopsComingContainer = screen.getByTestId(
SEND_NOTIFICATION_TEST_ID,
);
const alertWhenDataStopsComingContainer = screen
.getByText(ALERT_WHEN_DATA_STOPS_COMING_TEXT)
.closest(ADVANCED_OPTION_ITEM_CLASS);
const alertWhenDataStopsComingSwitch =
alertWhenDataStopsComingContainer?.querySelector(
SWITCH_ROLE_SELECTOR,
@@ -97,9 +94,9 @@ describe('AdvancedOptions', () => {
const collapse = screen.getByRole('button', { name: /ADVANCED OPTIONS/i });
fireEvent.click(collapse);
const minimumDataRequiredContainer = screen.getByTestId(
ENFORCE_MINIMUM_DATAPOINTS_TEST_ID,
);
const minimumDataRequiredContainer = screen
.getByText(MINIMUM_DATA_REQUIRED_TEXT)
.closest(ADVANCED_OPTION_ITEM_CLASS);
const minimumDataRequiredSwitch = minimumDataRequiredContainer?.querySelector(
SWITCH_ROLE_SELECTOR,
) as HTMLElement;
@@ -119,17 +116,15 @@ describe('AdvancedOptions', () => {
});
});
// TODO: Update when account for data delay is implemented - will need a data-testid
it.skip('"Account for data delay" works as expected', () => {
render(<AdvancedOptions />);
const collapse = screen.getByRole('button', { name: /ADVANCED OPTIONS/i });
fireEvent.click(collapse);
// This test needs a data-testid on the account for data delay component
const accountForDataDelayContainer = screen.getByTestId(
'account-for-data-delay-container',
);
const accountForDataDelayContainer = screen
.getByText(ACCOUNT_FOR_DATA_DELAY_TEXT)
.closest(ADVANCED_OPTION_ITEM_CLASS);
const accountForDataDelaySwitch = accountForDataDelayContainer?.querySelector(
SWITCH_ROLE_SELECTOR,
) as HTMLElement;

View File

@@ -16,7 +16,7 @@ jest.spyOn(alertState, 'useCreateAlertState').mockReturnValue(
const mockSetIsEvaluationCadenceDetailsVisible = jest.fn();
const mockSetIsPreviewVisible = jest.fn();
const EDIT_CUSTOM_SCHEDULE_TEST_ID = 'edit-custom-schedule';
const EDIT_CUSTOM_SCHEDULE_TEST_ID = '.edit-custom-schedule';
describe('EditCustomSchedule', () => {
it('should render the correct display text for custom mode with daily occurrence', () => {
@@ -47,7 +47,9 @@ describe('EditCustomSchedule', () => {
);
// Use textContent to verify the complete text across multiple Typography components
const container = screen.getByTestId(EDIT_CUSTOM_SCHEDULE_TEST_ID);
const container = screen
.getByText('Every')
.closest(EDIT_CUSTOM_SCHEDULE_TEST_ID);
expect(container).toHaveTextContent('EveryDayat00:00:00');
});
@@ -79,7 +81,9 @@ describe('EditCustomSchedule', () => {
/>,
);
const container = screen.getByTestId(EDIT_CUSTOM_SCHEDULE_TEST_ID);
const container = screen
.getByText('Every')
.closest(EDIT_CUSTOM_SCHEDULE_TEST_ID);
expect(container).toHaveTextContent(
'EveryWeekonMonday, Tuesday, Wednesday, Thursday, Fridayat00:00:00',
);
@@ -113,7 +117,9 @@ describe('EditCustomSchedule', () => {
/>,
);
const container = screen.getByTestId(EDIT_CUSTOM_SCHEDULE_TEST_ID);
const container = screen
.getByText('Every')
.closest(EDIT_CUSTOM_SCHEDULE_TEST_ID);
expect(container).toHaveTextContent('EveryMonthon1at00:00:00');
});

View File

@@ -12,15 +12,12 @@ const mockEvaluationWindow: EvaluationWindowState =
createMockEvaluationWindowState();
const mockSetEvaluationWindow = jest.fn();
const EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS =
'.evaluation-window-content-list-item';
const EVALUATION_WINDOW_DETAILS_TEST_ID = 'evaluation-window-details';
const ENTER_VALUE_PLACEHOLDER = 'Enter value';
const EVALUATION_WINDOW_TEXT = 'EVALUATION WINDOW';
// Test IDs for window type and timeframe options
const WINDOW_TYPE_ROLLING_TEST_ID = 'window-type-option-rolling';
const WINDOW_TYPE_CUMULATIVE_TEST_ID = 'window-type-option-cumulative';
const TIMEFRAME_LAST_5_MINUTES_TEST_ID = 'timeframe-option-5m0s';
const TIMEFRAME_CURRENT_HOUR_TEST_ID = 'timeframe-option-currentHour';
const LAST_5_MINUTES_TEXT = 'Last 5 minutes';
jest.mock('../EvaluationWindowPopover/EvaluationWindowDetails', () => ({
__esModule: true,
@@ -52,11 +49,15 @@ describe('EvaluationWindowPopover', () => {
EVALUATION_WINDOW_TYPE.forEach((option) => {
expect(screen.getByText(option.label)).toBeInTheDocument();
});
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
expect(rollingItem).toHaveAttribute('data-active', 'true');
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(rollingItem).toHaveClass('active');
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
expect(cumulativeItem).toHaveAttribute('data-active', 'false');
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(cumulativeItem).not.toHaveClass('active');
});
it('should render all window type options with cumulative selected', () => {
@@ -72,10 +73,14 @@ describe('EvaluationWindowPopover', () => {
expect(screen.getByText(option.label)).toBeInTheDocument();
});
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
expect(cumulativeItem).toHaveAttribute('data-active', 'true');
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
expect(rollingItem).toHaveAttribute('data-active', 'false');
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(cumulativeItem).toHaveClass('active');
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(rollingItem).not.toHaveClass('active');
});
it('should render all timeframe options in rolling mode with last 5 minutes selected by default', () => {
@@ -88,8 +93,10 @@ describe('EvaluationWindowPopover', () => {
EVALUATION_WINDOW_TIMEFRAME.rolling.forEach((option) => {
expect(screen.getByText(option.label)).toBeInTheDocument();
});
const last5MinutesItem = screen.getByTestId(TIMEFRAME_LAST_5_MINUTES_TEST_ID);
expect(last5MinutesItem).toHaveAttribute('data-active', 'true');
const last5MinutesItem = screen
.getByText(LAST_5_MINUTES_TEXT)
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(last5MinutesItem).toHaveClass('active');
});
it('should render all timeframe options in cumulative mode with current hour selected by default', () => {
@@ -105,8 +112,10 @@ describe('EvaluationWindowPopover', () => {
EVALUATION_WINDOW_TIMEFRAME.cumulative.forEach((option) => {
expect(screen.getByText(option.label)).toBeInTheDocument();
});
const currentHourItem = screen.getByTestId(TIMEFRAME_CURRENT_HOUR_TEST_ID);
expect(currentHourItem).toHaveAttribute('data-active', 'true');
const currentHourItem = screen
.getByText('Current hour')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
expect(currentHourItem).toHaveClass('active');
});
it('renders help text in details section for rolling mode with non-custom timeframe', () => {
@@ -178,11 +187,15 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
rollingItem?.focus();
fireEvent.keyDown(rollingItem, { key: 'ArrowDown' });
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS);
expect(cumulativeItem).toHaveFocus();
});
@@ -194,11 +207,15 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
cumulativeItem?.focus();
fireEvent.keyDown(cumulativeItem, { key: 'ArrowUp' });
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS);
expect(rollingItem).toHaveFocus();
});
@@ -210,11 +227,15 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
rollingItem?.focus();
fireEvent.keyDown(rollingItem, { key: 'ArrowRight' });
const timeframeItem = screen.getByTestId(TIMEFRAME_LAST_5_MINUTES_TEST_ID);
const timeframeItem = screen
.getByText(LAST_5_MINUTES_TEXT)
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS);
expect(timeframeItem).toHaveFocus();
});
@@ -226,11 +247,15 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const timeframeItem = screen.getByTestId(TIMEFRAME_LAST_5_MINUTES_TEST_ID);
const timeframeItem = screen
.getByText(LAST_5_MINUTES_TEXT)
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
timeframeItem?.focus();
fireEvent.keyDown(timeframeItem, { key: 'ArrowLeft' });
const rollingItem = screen.getByTestId(WINDOW_TYPE_ROLLING_TEST_ID);
const rollingItem = screen
.getByText('Rolling')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS);
expect(rollingItem).toHaveFocus();
});
@@ -242,7 +267,9 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
cumulativeItem?.focus();
fireEvent.keyDown(cumulativeItem, { key: 'Enter' });
@@ -260,7 +287,9 @@ describe('EvaluationWindowPopover', () => {
/>,
);
const cumulativeItem = screen.getByTestId(WINDOW_TYPE_CUMULATIVE_TEST_ID);
const cumulativeItem = screen
.getByText('Cumulative')
.closest(EVALUATION_WINDOW_CONTENT_LIST_ITEM_CLASS) as HTMLElement;
cumulativeItem?.focus();
fireEvent.keyDown(cumulativeItem, { key: ' ' });

View File

@@ -1,302 +0,0 @@
.evaluationSettingsContainer {
margin: var(--spacing-8);
}
.evaluateAlertConditionsContainer {
display: flex;
align-items: center;
gap: var(--spacing-8);
background-color: var(--l2-background);
padding: var(--spacing-8);
border-radius: 4px;
border: 1px solid var(--l1-border);
margin-bottom: var(--spacing-8);
:global(.ant-typography) {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
}
:global(.ant-btn) {
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
}
.evaluateAlertConditionsSeparator {
flex: 1;
height: 1px;
border-top: 1px dashed var(--l1-border);
}
.evaluateAlertConditionsButtonLeft {
color: var(--l2-foreground);
font-size: 12px;
padding-right: var(--spacing-8);
}
.evaluateAlertConditionsButtonRight {
display: flex;
align-items: center;
color: var(--muted-foreground);
gap: var(--spacing-4);
}
.evaluateAlertConditionsButtonRightText {
font-size: 12px;
font-weight: var(--font-weight-medium);
background-color: var(--l2-background);
padding: 1px var(--spacing-2);
}
.advancedOptionsContainer {
:global(.ant-collapse) {
:global(.ant-collapse-item) {
:global(.ant-collapse-header) {
background-color: var(--card);
border: 1px solid var(--l1-border);
:global(.ant-collapse-header-text) {
color: var(--muted-foreground);
font-family: Inter;
}
}
:global(.ant-collapse-content) {
:global(.ant-collapse-content-box) {
background-color: var(--card);
}
}
}
}
}
.condensedEvaluationSettingsContainer {
:global(.ant-btn) {
display: flex;
align-items: center;
min-width: 240px;
width: auto;
justify-content: space-between;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
}
}
.evaluateAlertConditionsButtonLeft {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-small);
flex-shrink: 0;
}
.evaluateAlertConditionsButtonRight {
display: flex;
align-items: center;
color: var(--l2-foreground);
gap: var(--spacing-4);
flex-shrink: 0;
}
.evaluateAlertConditionsButtonRightText {
font-size: var(--periscope-font-size-small);
font-weight: 500;
background-color: var(--l1-border);
padding: 1px 4px;
}
:global(.evaluation-window-popover-overlay) {
:global(.ant-popover-arrow) {
display: none !important;
}
:global(.ant-popover-content) {
background-color: var(--card);
border: 1px solid var(--l1-border);
border-radius: 4px;
padding: 0;
margin: 10px;
}
:global(.ant-popover-inner) {
background-color: var(--l2-background);
border: none;
padding: 0;
}
}
.evaluationWindowPopover {
min-width: 500px;
}
.evaluationWindowContent {
display: flex;
}
.evaluationWindowContentItem {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
border-right: 1px solid var(--l1-border);
padding: var(--spacing-6) var(--spacing-8);
min-width: 250px;
min-height: 300px;
}
.evaluationWindowContentItemLabel {
color: var(--muted-foreground);
font-size: var(--periscope-font-size-small);
line-height: 18px;
font-weight: var(--font-weight-medium);
}
.evaluationWindowContentList {
display: flex;
flex-direction: column;
}
.evaluationWindowContentListItem {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 calc(-1 * var(--spacing-8));
padding: var(--spacing-2) var(--spacing-8);
:global(.ant-typography) {
color: var(--muted-foreground);
font-weight: 400;
}
&:hover {
cursor: pointer;
background-color: var(--l1-background);
}
}
.evaluationWindowContentListItemActive {
background-color: var(--l1-background);
border-left: 2px solid var(--bg-robin-500);
:global(.ant-typography) {
font-weight: var(--font-weight-medium);
color: var(--l1-foreground);
}
}
.selectionContent {
padding: var(--spacing-8);
display: flex;
flex-direction: column;
gap: var(--spacing-8);
width: 400px;
:global(.ant-typography) {
color: var(--muted-foreground);
}
:global(.ant-btn) {
width: fit-content;
}
}
.evaluationWindowFooter {
display: flex;
justify-content: flex-end;
gap: var(--spacing-4);
background-color: var(--l2-background);
border-top: 1px solid var(--l1-border);
padding: var(--spacing-8);
:global(.ant-btn) {
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
}
}
.evaluationWindowDetails {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
width: 400px;
min-height: 300px;
padding: var(--spacing-8);
:global(.ant-typography) {
color: var(--l2-foreground);
font-size: var(--periscope-font-size-base);
font-weight: var(--font-weight-medium);
}
:global(.ant-select) {
width: 60%;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
:global(.ant-select-selector) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
&:hover {
border-color: var(--l1-border);
}
}
}
.selectGroup {
display: flex;
flex-direction: column;
gap: 2px;
:global(.ant-typography) {
color: var(--l3-foreground);
font-size: var(--periscope-font-size-small);
line-height: 18px;
font-weight: var(--font-weight-medium);
}
:global(.ant-input) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
width: 60%;
}
}
.timeSelectGroup {
:global(.ant-input-group) {
flex-direction: row;
gap: var(--spacing-4);
:global(.ant-select) {
width: 40px;
:global(.ant-select-selector) {
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
}
}

View File

@@ -0,0 +1,266 @@
.evaluation-settings-container {
margin: 16px;
.evaluate-alert-conditions-container {
display: flex;
align-items: center;
gap: 16px;
background-color: var(--l2-background);
padding: 16px;
border-radius: 4px;
border: 1px solid var(--l1-border);
margin-bottom: 16px;
.ant-typography {
color: var(--l2-foreground);
font-size: 13px;
}
.evaluate-alert-conditions-separator {
flex: 1;
height: 1px;
border-top: 1px dashed var(--l1-border);
}
.ant-btn {
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
.evaluate-alert-conditions-button-left {
color: var(--l2-foreground);
font-size: 12px;
padding-right: 16px;
}
.evaluate-alert-conditions-button-right {
display: flex;
align-items: center;
color: var(--muted-foreground);
gap: 8px;
.evaluate-alert-conditions-button-right-text {
font-size: 12px;
font-weight: 500;
background-color: var(--l2-background);
padding: 1px 4px;
}
}
}
}
}
.advanced-options-container {
.ant-collapse {
.ant-collapse-item {
.ant-collapse-header {
background-color: var(--card);
border: 1px solid var(--l1-border);
.ant-collapse-header-text {
color: var(--muted-foreground);
font-family: Inter;
}
}
.ant-collapse-content {
.ant-collapse-content-box {
background-color: var(--card);
}
}
}
}
}
.ant-popover-arrow {
display: none !important;
}
.ant-popover-content {
background-color: var(--card);
border: 1px solid var(--l1-border);
border-radius: 4px;
padding: 0;
margin: 10px;
.ant-popover-inner {
background-color: var(--l2-background);
border: none;
padding: 0;
.evaluation-window-popover {
min-width: 500px;
.evaluation-window-content {
display: flex;
.evaluation-window-content-item {
display: flex;
flex-direction: column;
gap: 8px;
border-right: 1px solid var(--l1-border);
padding: 12px 16px;
min-width: 250px;
min-height: 300px;
.evaluation-window-content-item-label {
color: var(--muted-foreground);
font-size: 11px;
line-height: 18px;
font-weight: 500;
}
.evaluation-window-content-list {
display: flex;
flex-direction: column;
.evaluation-window-content-list-item {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 -16px;
padding: 4px 16px;
.ant-typography {
color: var(--muted-foreground);
font-weight: 400;
}
&.active {
background-color: var(--l1-background);
border-left: 2px solid var(--bg-robin-500);
.ant-typography {
font-weight: 500;
color: var(--l1-foreground);
}
}
&:hover {
cursor: pointer;
background-color: var(--l1-background);
}
}
}
}
.selection-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
width: 400px;
.ant-typography {
color: var(--muted-foreground);
}
.ant-btn {
width: fit-content;
}
}
}
.evaluation-window-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
background-color: var(--l2-background);
border-top: 1px solid var(--l1-border);
padding: 16px;
}
.ant-btn {
background-color: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
font-size: 13px;
}
}
}
}
.evaluation-window-details {
display: flex;
flex-direction: column;
gap: 16px;
width: 400px;
min-height: 300px;
padding: 16px;
.select-group {
display: flex;
flex-direction: column;
gap: 2px;
.ant-typography {
color: var(--l3-foreground);
font-size: 11px;
line-height: 18px;
font-weight: 500;
}
}
.time-select-group {
.ant-input-group {
flex-direction: row;
gap: 8px;
.ant-select {
width: 40px;
.ant-select-selector {
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
}
}
.ant-typography {
color: var(--l2-foreground);
font-size: 13px;
font-weight: 500;
}
.ant-select {
width: 60%;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
.ant-select-selector {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
&:hover {
border-color: var(--l1-border);
}
}
.select-group .ant-input:not(.time-input-field) {
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
height: 32px;
width: 60%;
}
}

View File

@@ -1,19 +0,0 @@
.footer {
position: fixed;
bottom: 0;
left: 63px;
right: 0;
background-color: var(--l1-background);
border-top: 1px solid var(--l1-border);
padding: var(--spacing-6);
z-index: 1000;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.buttonGroup {
display: flex;
gap: var(--spacing-6);
}

View File

@@ -19,7 +19,7 @@ import {
validateCreateAlertState,
} from './utils';
import styles from './Footer.module.scss';
import './styles.scss';
import {
invalidateGetRuleByID,
invalidateListRules,
@@ -243,7 +243,7 @@ function Footer(): JSX.Element {
]);
return (
<div className={styles.footer}>
<div className="create-alert-v2-footer">
<Button
variant="solid"
color="secondary"
@@ -252,7 +252,7 @@ function Footer(): JSX.Element {
>
<X size={14} /> Discard
</Button>
<div className={styles.buttonGroup}>
<div className="button-group">
{testAlertButton}
{saveAlertButton}
</div>

View File

@@ -0,0 +1,31 @@
.create-alert-v2-footer {
position: fixed;
bottom: 0;
left: 63px;
right: 0;
background-color: var(--l1-background);
border-top: 1px solid var(--l1-border);
padding: 12px;
z-index: 1000;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
align-items: center;
.button-group {
display: flex;
gap: 12px;
}
.ant-btn {
display: flex;
align-items: center;
gap: 8px;
}
.ant-btn-default {
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
}
}

View File

@@ -7,8 +7,6 @@ import { Info } from '@signozhq/icons';
import { ALL_SELECTED_VALUE } from '../constants';
import { useCreateAlertState } from '../context';
import styles from './NotificationSettings.module.scss';
function MultipleNotifications(): JSX.Element {
const { notificationSettings, setNotificationSettings } =
useCreateAlertState();
@@ -101,7 +99,7 @@ function MultipleNotifications(): JSX.Element {
data-testid="multiple-notifications-select"
/>
{isMultipleNotificationsEnabled && (
<Typography.Text className={styles.multipleNotificationsSelectDescription}>
<Typography.Text className="multiple-notifications-select-description">
{groupByDescription}
</Typography.Text>
)}
@@ -124,15 +122,15 @@ function MultipleNotifications(): JSX.Element {
]);
return (
<div className={styles.multipleNotificationsContainer}>
<div className={styles.multipleNotificationsHeader}>
<Typography.Text className={styles.multipleNotificationsHeaderTitle}>
<div className="multiple-notifications-container">
<div className="multiple-notifications-header">
<Typography.Text className="multiple-notifications-header-title">
Group alerts by{' '}
<Tooltip title="Group similar alerts together to reduce notification volume. Leave empty to combine all matching alerts into one notification without grouping.">
<Info size={16} />
</Tooltip>
</Typography.Text>
<Typography.Text className={styles.multipleNotificationsHeaderDescription}>
<Typography.Text className="multiple-notifications-header-description">
Combine alerts with the same field values into a single notification.
</Typography.Text>
</div>

View File

@@ -4,8 +4,6 @@ import { Info } from '@signozhq/icons';
import { useCreateAlertState } from '../context';
import styles from './NotificationSettings.module.scss';
function NotificationMessage(): JSX.Element {
const { notificationSettings, setNotificationSettings } =
useCreateAlertState();
@@ -52,21 +50,21 @@ function NotificationMessage(): JSX.Element {
// );
return (
<div className={styles.notificationMessageContainer}>
<div className={styles.notificationMessageHeader}>
<div className={styles.notificationMessageHeaderContent}>
<Typography.Text className={styles.notificationMessageHeaderTitle}>
<div className="notification-message-container">
<div className="notification-message-header">
<div className="notification-message-header-content">
<Typography.Text className="notification-message-header-title">
Notification Message
<Tooltip title="Customize the message content sent in alert notifications. Template variables like {{alertname}}, {{value}}, and {{threshold}} will be replaced with actual values when the alert fires.">
<Info size={16} />
</Tooltip>
</Typography.Text>
<Typography.Text className={styles.notificationMessageHeaderDescription}>
<Typography.Text className="notification-message-header-description">
Custom message content for alert notifications. Use template variables to
include dynamic information.
</Typography.Text>
</div>
<div className={styles.notificationMessageHeaderActions}>
<div className="notification-message-header-actions">
{/* TODO: Add back when the functionality is implemented */}
{/* <Popover content={templateVariableContent}>
<Button type="text">

View File

@@ -1,262 +0,0 @@
.notificationSettingsContainer {
display: flex;
flex-direction: column;
margin: 0 var(--spacing-8);
}
.notificationMessageContainer {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
margin-top: -8px;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
padding: var(--spacing-8);
textarea {
height: 150px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: Inter;
font-size: var(--periscope-font-size-base);
}
}
.notificationMessageHeader {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--spacing-8);
}
.notificationMessageHeaderContent {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
}
.notificationMessageHeaderTitle {
--typography-text-display: flex;
gap: var(--spacing-4);
align-items: center;
color: var(--l1-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 500;
}
.notificationMessageHeaderDescription {
color: var(--l2-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 400;
}
.notificationMessageHeaderActions {
:global(.ant-btn) {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
color: var(--bg-robin-400);
}
}
.notificationSettingsContent {
display: flex;
flex-direction: column;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
padding: var(--spacing-8);
margin-top: var(--spacing-8);
}
.repeatNotificationsInput {
display: flex;
align-items: center;
gap: var(--spacing-4);
:global(.ant-input) {
width: 120px;
border: 1px solid var(--l1-border);
}
:global(.ant-select) {
:global(.ant-select-selector) {
width: 120px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
:global(.ant-select-multiple) {
:global(.ant-select-selector) {
width: 200px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
}
.multipleNotificationsContainer {
display: flex;
padding: 4px var(--spacing-8) var(--spacing-8) var(--spacing-8);
border-bottom: 1px solid var(--l1-border);
justify-content: space-between;
:global(.ant-select) {
width: 300px;
}
}
.multipleNotificationsHeader {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
:global(.ant-typography) {
--typography-text-display: flex;
gap: 4px;
align-items: center;
}
}
.multipleNotificationsHeaderTitle {
color: var(--l1-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 500;
--typography-text-display: flex;
align-items: center;
gap: var(--spacing-4);
}
.multipleNotificationsHeaderDescription {
color: var(--l2-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 400;
}
.multipleNotificationsSelectDescription {
font-size: var(--periscope-font-size-small);
color: var(--l2-foreground);
margin-top: 4px;
--typography-text-display: block;
}
.reNotificationContainer {
display: flex;
flex-direction: column;
gap: var(--spacing-8);
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
padding: var(--spacing-8);
margin-top: var(--spacing-8);
}
.advancedOptionItem {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--spacing-8);
}
.advancedOptionItemLeftContent {
display: flex;
flex-direction: column;
gap: var(--spacing-3);
}
.advancedOptionItemTitle {
color: var(--l2-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 500;
}
.advancedOptionItemDescription {
color: var(--muted-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-base);
font-weight: 400;
}
.borderBottom {
border-bottom: 1px solid var(--l1-border);
width: 100%;
margin-left: -16px;
margin-right: -32px;
}
.reNotificationCondition {
display: flex;
align-items: center;
gap: var(--spacing-4);
flex-wrap: nowrap;
:global(.ant-typography) {
font-size: var(--periscope-font-size-base);
font-weight: 400;
color: var(--l2-foreground);
white-space: nowrap;
}
:global(.ant-select) {
width: 200px;
height: 32px;
flex-shrink: 0;
:global(.ant-select-selector) {
border: 1px solid var(--l1-border);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
}
:global(.ant-input) {
width: 200px;
flex-shrink: 0;
border: 1px solid var(--l1-border);
}
}
.templateVariableContent {
padding: var(--spacing-8);
display: flex;
flex-direction: column;
gap: 2px;
}
.templateVariableContentItem {
display: flex;
gap: var(--spacing-4);
align-items: center;
code {
background-color: var(--l1-background);
color: var(--l2-foreground);
padding: 2px 4px;
}
}

View File

@@ -12,14 +12,14 @@ import Stepper from '../Stepper';
import MultipleNotifications from './MultipleNotifications';
import NotificationMessage from './NotificationMessage';
import styles from './NotificationSettings.module.scss';
import './styles.scss';
function NotificationSettings(): JSX.Element {
const { notificationSettings, setNotificationSettings } =
useCreateAlertState();
const repeatNotificationsInput = (
<div className={styles.repeatNotificationsInput}>
<div className="repeat-notifications-input">
<Typography.Text>Every</Typography.Text>
<Input
value={notificationSettings.reNotification.value}
@@ -81,10 +81,10 @@ function NotificationSettings(): JSX.Element {
);
return (
<div className={styles.notificationSettingsContainer}>
<div className="notification-settings-container">
<Stepper stepNumber={3} label="Notification settings" />
<NotificationMessage />
<div className={styles.notificationSettingsContent}>
<div className="notification-settings-content">
<MultipleNotifications />
<AdvancedOptionItem
title="Repeat notifications"

View File

@@ -0,0 +1,261 @@
.notification-settings-container {
display: flex;
flex-direction: column;
margin: 0 16px;
.notification-message-container {
display: flex;
flex-direction: column;
gap: 16px;
margin-top: -8px;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
padding: 16px;
.notification-message-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 16px;
.notification-message-header-content {
display: flex;
flex-direction: column;
gap: 8px;
.notification-message-header-title {
display: flex;
gap: 8px;
align-items: center;
color: var(--l1-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 500;
}
.notification-message-header-description {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 400;
}
}
.notification-message-header-actions {
.ant-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 2px;
color: var(--bg-robin-400);
}
}
}
textarea {
height: 150px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border-radius: 4px;
color: var(--l2-foreground) !important;
font-family: Inter;
font-size: 13px;
}
}
.notification-settings-content {
display: flex;
flex-direction: column;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
padding: 16px;
margin-top: 16px;
.repeat-notifications-input {
display: flex;
align-items: center;
gap: 8px;
.ant-input {
width: 120px;
border: 1px solid var(--l1-border);
}
.ant-select {
.ant-select-selector {
width: 120px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
.ant-select-multiple {
.ant-select-selector {
width: 200px;
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
}
}
}
}
.multiple-notifications-container {
display: flex;
padding: 4px 16px 16px 16px;
border-bottom: 1px solid var(--l1-border);
justify-content: space-between;
.multiple-notifications-header {
display: flex;
flex-direction: column;
gap: 8px;
.ant-typography {
display: flex;
gap: 4px;
align-items: center;
}
.multiple-notifications-header-title {
color: var(--l1-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
}
.multiple-notifications-header-description {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 400;
}
}
.ant-select {
width: 300px;
}
.multiple-notifications-select-description {
font-size: 10px;
color: var(--l2-foreground);
margin-top: 4px;
display: block;
}
}
.re-notification-container {
display: flex;
flex-direction: column;
gap: 16px;
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
padding: 16px;
margin-top: 16px;
.advanced-option-item {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 16px;
.advanced-option-item-left-content {
display: flex;
flex-direction: column;
gap: 6px;
.advanced-option-item-title {
color: var(--l2-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 500;
}
.advanced-option-item-description {
color: var(--muted-foreground);
font-family: Inter;
font-size: 13px;
font-weight: 400;
}
}
}
.border-bottom {
border-bottom: 1px solid var(--l1-border);
width: 100%;
margin-left: -16px;
margin-right: -32px;
}
.re-notification-condition {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: nowrap;
.ant-typography {
font-size: 13px;
font-weight: 400;
color: var(--l2-foreground);
white-space: nowrap;
}
.ant-select {
width: 200px;
height: 32px;
flex-shrink: 0;
.ant-select-selector {
border: 1px solid var(--l1-border);
&:hover {
border-color: var(--l1-border);
}
&:focus {
border-color: var(--l1-border);
box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.1);
}
}
}
.ant-input {
width: 200px;
flex-shrink: 0;
border: 1px solid var(--l1-border);
}
}
}
}
}
.template-variable-content {
padding: 16px;
display: flex;
flex-direction: column;
gap: 2px;
.template-variable-content-item {
display: flex;
gap: 8px;
align-items: center;
code {
background-color: var(--l1-background);
color: var(--l2-foreground);
padding: 2px 4px;
}
}
}

View File

@@ -1,21 +0,0 @@
.chartPreviewContainer {
height: 100%;
width: 100%;
margin-right: var(--spacing-2);
:global(.ant-card) {
border: 1px solid var(--l1-border);
:global(.ant-card-body) {
background-color: var(--l1-background);
}
}
}
.chartPreviewHeadline {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--spacing-4);
width: 100%;
}

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