Compare commits

..

101 Commits

Author SHA1 Message Date
Shaheer Kochai
87f1597d4e fix: prevent overwriting query expression and queryName on switching between panel types (#5430) 2024-07-09 08:13:35 +04:30
Shaheer Kochai
916663b4d5 fix: fix the explorer toolbar buttons padding (#5443) 2024-07-09 08:12:25 +04:30
Shaheer Kochai
b0e355eb64 fix: properly render \n and \t in log details + apply Geist Mono font to the logs (#5347)
* fix: properly render newline and tab in log details

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

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

* feat: cdoe refactor and test case changes

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

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

* feat: added en-gb strings

* feat: added more text to strings

* feat: removed commented code and app.ts changes

* feat: added test case for onboarding and invite flow

* feat: added invite team member logEvents

* feat: resovled comments

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

* chore: skip flaky test

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

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

* feat: test case skip

* feat: fixed import order

* feat: added utm parameter link

* feat: added strings

* feat: resovled comments

* feat: added common doclinks util

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

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

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

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

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

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

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

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

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

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

* feat: comment resolve and code refactor

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

* feat: removed testSuccess variable and used responseStatus directly

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

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

* feat: code refactor

* feat: added status message for save and test

* feat: added status message for save channel events

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

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

* fix: removed locust_count and hatch_rate options

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

---------

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

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

* chore: remove console log

---------

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

* feat: posthog events

---------

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

* chore: mapped paths

* chore: fixed instructions

* fix: added central collector steps

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

* fix: removed return true

---------

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

* fix: added isArray check
2024-06-21 12:55:27 +05:30
Rajat Dabade
afcee9cd02 refactor: add to query should not open log detail drawer (#4732)
Co-authored-by: Rajat-Dabade <rajat@signoz.io>
2024-06-21 11:46:04 +05:30
Prashant Shahi
9dbef080c6 Merge pull request #5288 from SigNoz/release/v0.48.x
Release/v0.48.x
2024-06-20 20:49:47 +05:30
Vikrant Gupta
82a079e687 fix: move date time picker to click rather than hover (#5296) 2024-06-20 19:19:42 +05:30
Prashant Shahi
6c192f1242 Merge branch 'develop' into release/v0.48.x 2024-06-20 18:46:14 +05:30
Yunus M
adfeaaa1f0 feat: pass fill gaps to query range api (#5276) 2024-06-20 18:34:05 +05:30
Srikanth Chekuri
6ee9705599 chore: bump SigNoz/signoz-otel-collector and SigNoz/prometheus (#5294) 2024-06-20 18:33:45 +05:30
Vikrant Gupta
67965c8e4d fix: dependent variable panel not updating (#5283)
* fix: dependent variable panel not updating

* fix: build issues
2024-06-20 17:21:04 +05:30
Yunus M
38b1de5ccc feat: [5038] enable list view - add to dashboard (#5268)
* feat: [5038] enable list view - add to dashboard

* feat: pass page size for list view export
2024-06-20 17:05:18 +05:30
Yunus M
64e06ab3f9 fix: update from typography link to react router dom link component to maintain global state (#5279) 2024-06-20 11:34:27 +05:30
Prashant Shahi
537641000d chore(signoz): 📌 pin versions: SigNoz 0.48.0, SigNoz OtelCollector 0.102.0
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-06-19 21:14:45 +05:30
Prashant Shahi
4916cf5083 Merge branch 'main' into release/v0.48.x 2024-06-19 20:57:30 +05:30
Vikrant Gupta
f3c2fb0246 fix: dashboard empty state learn more link not working (#5287) 2024-06-19 20:31:36 +05:30
Nityananda Gohain
a4e98e565d feat: sanitize query and remove groupBy for list panel query (#5285) 2024-06-19 15:40:34 +05:30
Srikanth Chekuri
faa1728b8c chore: threshold rule panel type to graph (#5284) 2024-06-19 14:19:30 +05:30
SagarRajput-7
b69e97d7b0 fix: fixed flakiness in alert list actions - delete, edit, clone & toggle (#5237)
* fix: fixed flakiness in alert list actions - delete, edit, clone & toggle

* fix: added onhover dropdown open and close
2024-06-19 12:10:43 +05:30
Vikrant Gupta
c0195e9dc9 fix: added null checks for stacked bar chart with fallbacks (#5282) 2024-06-19 11:50:18 +05:30
Vishal Sharma
b69545a771 fix: update companyDomain in before firing every event (#5275) 2024-06-19 10:49:57 +05:30
Vikrant Gupta
9a6db272c1 fix: update the error boundary components with sentry error boundary components (#5271)
* fix: update the error boundary components with sentry error boundary components

* fix: update fallback components
2024-06-18 19:04:06 +05:30
SagarRajput-7
45d6430ab3 fix: fixed panelType when creating alerts from histogram dashboard (#5251)
* fix: fixed panelType when creating alerts from histogram dashboard

* fix: added PANEL_TYPES.TIME_SERIES always for all type in case of alerts
2024-06-18 13:48:28 +05:30
SagarRajput-7
cf7bf32ac2 fix: fixed lightMode style for histogram panel (#5236) 2024-06-18 13:38:30 +05:30
SagarRajput-7
1695b4f06d fix: convert timestamp in new trace explorer to user browser timezone and readable format (#5235)
* fix: convert timestamp in new trace explorer to user browser timezone and readable format

* fix: code refactor
2024-06-18 13:24:54 +05:30
SagarRajput-7
a65d5095a0 feat: added checkbox selection in dashboard variables (#5191)
* feat: added checkbox selection in dashboard variables

* feat: added checkbox selection - handling with only and all

* feat: added checkbox selection - style changes

* fix: fixed deselecting all options

* feat: fixed all showing up in single select

* feat: improve styles

* feat: fixed single select getting all values and array issues

* feat: updated test case

* feat: added max tag shown logic with count length and info on hover for overflowed content
2024-06-18 13:02:15 +05:30
Vikrant Gupta
0fade428ef fix: table row data doesn't align with the response (#5248)
* fix: wrong values getting associated with the table rows

* fix: table columns rendering

* fix: remove console logs
2024-06-18 12:25:10 +05:30
Vikrant Gupta
3b4b9e43b3 fix: trace explorer not highlighting in sidenav (#5263) 2024-06-18 11:58:35 +05:30
Srikanth Chekuri
c104b758ba chore: adjust the step interval for builder queries (#5253) 2024-06-17 22:59:28 +05:30
Vikrant Gupta
2a4e97f8da fix: table sorting when units are present (#5249) 2024-06-17 15:51:04 +05:30
Srikanth Chekuri
f1b5da9916 chore: fix elapsed time formatting (#5238) 2024-06-17 09:00:55 +05:30
Prashant Shahi
b57a24a177 Merge pull request #5151 from SigNoz/release/v0.47.x
Release/v0.47.x
2024-06-05 19:51:47 +05:30
Prashant Shahi
a6e005e3a2 Merge branch 'develop' into release/v0.47.x 2024-06-05 19:43:32 +05:30
Prashant Shahi
4d375e7cc3 chore(signoz): 📌 pin versions: SigNoz 0.47.0, SigNoz OtelCollector 0.88.26
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-06-05 19:12:50 +05:30
268 changed files with 7654 additions and 2176 deletions

View File

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

View File

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

View File

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

View File

@@ -146,7 +146,7 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.46.0
image: signoz/query-service:0.49.1
command:
[
"-config=/root/config/prometheus.yml",
@@ -186,7 +186,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:0.46.0
image: signoz/frontend:0.48.0
deploy:
restart_policy:
condition: on-failure
@@ -199,7 +199,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
image: signoz/signoz-otel-collector:0.88.24
image: signoz/signoz-otel-collector:0.102.2
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -237,7 +237,7 @@ services:
- query-service
otel-collector-migrator:
image: signoz/signoz-schema-migrator:0.88.24
image: signoz/signoz-schema-migrator:0.102.2
deploy:
restart_policy:
condition: on-failure

View File

@@ -66,7 +66,7 @@ services:
- --storage.path=/data
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -81,7 +81,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector:
container_name: signoz-otel-collector
image: signoz/signoz-otel-collector:0.88.24
image: signoz/signoz-otel-collector:0.102.2
command:
[
"--config=/etc/otel-collector-config.yaml",

View File

@@ -164,7 +164,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.46.0}
image: signoz/query-service:${DOCKER_TAG:-0.49.1}
container_name: signoz-query-service
command:
[
@@ -204,7 +204,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.46.0}
image: signoz/frontend:${DOCKER_TAG:-0.49.1}
container_name: signoz-frontend
restart: on-failure
depends_on:
@@ -216,7 +216,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -230,7 +230,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.24}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2}
container_name: signoz-otel-collector
command:
[

View File

@@ -164,7 +164,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.46.0}
image: signoz/query-service:${DOCKER_TAG:-0.49.1}
container_name: signoz-query-service
command:
[
@@ -203,7 +203,7 @@ services:
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.46.0}
image: signoz/frontend:${DOCKER_TAG:-0.49.1}
container_name: signoz-frontend
restart: on-failure
depends_on:
@@ -215,7 +215,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.102.2}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
@@ -229,7 +229,7 @@ services:
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.24}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.102.2}
container_name: signoz-otel-collector
command:
[

View File

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

View File

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

View File

@@ -35,14 +35,14 @@ func (ah *APIHandler) loginUser(w http.ResponseWriter, r *http.Request) {
req := basemodel.LoginRequest{}
err := parseRequest(r, &req)
if err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
ctx := context.Background()
if req.Email != "" && ah.CheckFeature(model.SSO) {
var apierr *basemodel.ApiError
var apierr basemodel.BaseApiError
_, apierr = ah.AppDao().CanUsePassword(ctx, req.Email)
if apierr != nil && !apierr.IsNil() {
RespondError(w, apierr, nil)
@@ -74,7 +74,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
requestBody, err := io.ReadAll(r.Body)
if err != nil {
zap.L().Error("received no input in api", zap.Error(err))
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
@@ -82,7 +82,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
if err != nil {
zap.L().Error("received invalid user registration request", zap.Error(err))
RespondError(w, basemodel.BadRequest(fmt.Errorf("failed to register user")), nil)
RespondError(w, model.BadRequest(fmt.Errorf("failed to register user")), nil)
return
}
@@ -90,13 +90,13 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
invite, err := baseauth.ValidateInvite(ctx, req)
if err != nil {
zap.L().Error("failed to validate invite token", zap.Error(err))
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
if invite == nil {
zap.L().Error("failed to validate invite token: it is either empty or invalid", zap.Error(err))
RespondError(w, basemodel.BadRequest(basemodel.ErrSignupFailed{}), nil)
RespondError(w, model.BadRequest(basemodel.ErrSignupFailed{}), nil)
return
}
@@ -104,7 +104,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
domain, apierr := ah.AppDao().GetDomainByEmail(ctx, invite.Email)
if apierr != nil {
zap.L().Error("failed to get domain from email", zap.Error(apierr))
RespondError(w, basemodel.InternalError(basemodel.ErrSignupFailed{}), nil)
RespondError(w, model.InternalError(basemodel.ErrSignupFailed{}), nil)
}
precheckResp := &basemodel.PrecheckResponse{
@@ -120,7 +120,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
return
}
var precheckError *basemodel.ApiError
var precheckError basemodel.BaseApiError
precheckResp, precheckError = ah.AppDao().PrecheckLogin(ctx, user.Email, req.SourceUrl)
if precheckError != nil {
@@ -130,7 +130,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
} else {
// no-sso, validate password
if err := baseauth.ValidatePassword(req.Password); err != nil {
RespondError(w, basemodel.InternalError(fmt.Errorf("password is not in a valid format")), nil)
RespondError(w, model.InternalError(fmt.Errorf("password is not in a valid format")), nil)
return
}
@@ -155,7 +155,7 @@ func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
inviteObject, err := baseauth.GetInvite(context.Background(), token)
if err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/google/uuid"
"github.com/gorilla/mux"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
func (ah *APIHandler) listDomainsByOrg(w http.ResponseWriter, r *http.Request) {
@@ -28,12 +27,12 @@ func (ah *APIHandler) postDomain(w http.ResponseWriter, r *http.Request) {
req := model.OrgDomain{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
if err := req.ValidNew(); err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
@@ -51,18 +50,18 @@ func (ah *APIHandler) putDomain(w http.ResponseWriter, r *http.Request) {
domainIdStr := mux.Vars(r)["id"]
domainId, err := uuid.Parse(domainIdStr)
if err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
req := model.OrgDomain{Id: domainId}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
req.Id = domainId
if err := req.Valid(nil); err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
}
if apierr := ah.AppDao().UpdateDomain(ctx, &req); apierr != nil {
@@ -78,7 +77,7 @@ func (ah *APIHandler) deleteDomain(w http.ResponseWriter, r *http.Request) {
domainId, err := uuid.Parse(domainIdStr)
if err != nil {
RespondError(w, basemodel.BadRequest(fmt.Errorf("invalid domain id")), nil)
RespondError(w, model.BadRequest(fmt.Errorf("invalid domain id")), nil)
return
}

View File

@@ -9,7 +9,6 @@ import (
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
@@ -72,12 +71,12 @@ func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
var l model.License
if err := json.NewDecoder(r.Body).Decode(&l); err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
if l.Key == "" {
RespondError(w, basemodel.BadRequest(fmt.Errorf("license key is required")), nil)
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
return
}
license, apiError := ah.LM().Activate(r.Context(), l.Key)
@@ -101,20 +100,20 @@ func (ah *APIHandler) checkout(w http.ResponseWriter, r *http.Request) {
hClient := &http.Client{}
req, err := http.NewRequest("POST", constants.LicenseSignozIo+"/checkout", r.Body)
if err != nil {
RespondError(w, basemodel.InternalError(err), nil)
RespondError(w, model.InternalError(err), nil)
return
}
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
licenseResp, err := hClient.Do(req)
if err != nil {
RespondError(w, basemodel.InternalError(err), nil)
RespondError(w, model.InternalError(err), nil)
return
}
// decode response body
var resp checkoutResponse
if err := json.NewDecoder(licenseResp.Body).Decode(&resp); err != nil {
RespondError(w, basemodel.InternalError(err), nil)
RespondError(w, model.InternalError(err), nil)
return
}
@@ -125,7 +124,7 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
licenseKey := r.URL.Query().Get("licenseKey")
if licenseKey == "" {
RespondError(w, basemodel.BadRequest(fmt.Errorf("license key is required")), nil)
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
return
}
@@ -134,20 +133,20 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
hClient := &http.Client{}
req, err := http.NewRequest("GET", billingURL, nil)
if err != nil {
RespondError(w, basemodel.InternalError(err), nil)
RespondError(w, model.InternalError(err), nil)
return
}
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
billingResp, err := hClient.Do(req)
if err != nil {
RespondError(w, basemodel.InternalError(err), nil)
RespondError(w, model.InternalError(err), nil)
return
}
// decode response body
var billingResponse billingDetails
if err := json.NewDecoder(billingResp.Body).Decode(&billingResponse); err != nil {
RespondError(w, basemodel.InternalError(err), nil)
RespondError(w, model.InternalError(err), nil)
return
}
@@ -252,20 +251,20 @@ func (ah *APIHandler) portalSession(w http.ResponseWriter, r *http.Request) {
hClient := &http.Client{}
req, err := http.NewRequest("POST", constants.LicenseSignozIo+"/portal", r.Body)
if err != nil {
RespondError(w, basemodel.InternalError(err), nil)
RespondError(w, model.InternalError(err), nil)
return
}
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
licenseResp, err := hClient.Do(req)
if err != nil {
RespondError(w, basemodel.InternalError(err), nil)
RespondError(w, model.InternalError(err), nil)
return
}
// decode response body
var resp checkoutResponse
if err := json.NewDecoder(licenseResp.Body).Decode(&resp); err != nil {
RespondError(w, basemodel.InternalError(err), nil)
RespondError(w, model.InternalError(err), nil)
return
}

View File

@@ -31,13 +31,13 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
req := model.CreatePATRequestBody{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &basemodel.ApiError{
Typ: basemodel.ErrorUnauthorized,
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
Err: err,
}, nil)
return
@@ -49,7 +49,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
}
err = validatePATRequest(pat)
if err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
@@ -66,7 +66,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
}
zap.L().Info("Got Create PAT request", zap.Any("pat", pat))
var apierr *basemodel.ApiError
var apierr basemodel.BaseApiError
if pat, apierr = ah.AppDao().CreatePAT(ctx, pat); apierr != nil {
RespondError(w, apierr, nil)
return
@@ -93,14 +93,14 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
req := model.PAT{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &basemodel.ApiError{
Typ: basemodel.ErrorUnauthorized,
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
Err: err,
}, nil)
return
@@ -108,7 +108,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
err = validatePATRequest(req)
if err != nil {
RespondError(w, basemodel.BadRequest(err), nil)
RespondError(w, model.BadRequest(err), nil)
return
}
@@ -116,7 +116,7 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
req.UpdatedAt = time.Now().Unix()
zap.L().Info("Got Update PAT request", zap.Any("pat", req))
var apierr *basemodel.ApiError
var apierr basemodel.BaseApiError
if apierr = ah.AppDao().UpdatePAT(ctx, req, id); apierr != nil {
RespondError(w, apierr, nil)
return
@@ -129,8 +129,8 @@ func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
ctx := context.Background()
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &basemodel.ApiError{
Typ: basemodel.ErrorUnauthorized,
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
Err: err,
}, nil)
return
@@ -149,8 +149,8 @@ func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
user, err := auth.GetUserFromRequest(r)
if err != nil {
RespondError(w, &basemodel.ApiError{
Typ: basemodel.ErrorUnauthorized,
RespondError(w, &model.ApiError{
Typ: model.ErrorUnauthorized,
Err: err,
}, nil)
return

View File

@@ -7,6 +7,6 @@ import (
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
func RespondError(w http.ResponseWriter, apiErr *basemodel.ApiError, data interface{}) {
baseapp.RespondError(w, apiErr)
func RespondError(w http.ResponseWriter, apiErr basemodel.BaseApiError, data interface{}) {
baseapp.RespondError(w, apiErr, data)
}

View File

@@ -4,6 +4,7 @@ import (
"net/http"
"go.signoz.io/signoz/ee/query-service/app/db"
"go.signoz.io/signoz/ee/query-service/model"
baseapp "go.signoz.io/signoz/pkg/query-service/app"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
"go.uber.org/zap"
@@ -18,7 +19,7 @@ func (ah *APIHandler) searchTraces(w http.ResponseWriter, r *http.Request) {
}
searchTracesParams, err := baseapp.ParseSearchTracesParams(r)
if err != nil {
RespondError(w, &basemodel.ApiError{Typ: basemodel.ErrorBadData, Err: err}, "Error reading params")
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading params")
return
}

View File

@@ -21,7 +21,7 @@ import (
// GetMetricResultEE runs the query and returns list of time series
func (r *ClickhouseReader) GetMetricResultEE(ctx context.Context, query string) ([]*basemodel.Series, string, error) {
defer utils.Elapsed("GetMetricResult")()
defer utils.Elapsed("GetMetricResult", nil)()
zap.L().Info("Executing metric result query: ", zap.String("query", query))
var hash string

View File

@@ -64,7 +64,6 @@ type ServerOptions struct {
// alert specific params
DisableRules bool
RuleRepoURL string
PreferDelta bool
PreferSpanMetrics bool
MaxIdleConns int
MaxOpenConns int
@@ -256,7 +255,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
apiOpts := api.APIHandlerOptions{
DataConnector: reader,
SkipConfig: skipConfig,
PreferDelta: serverOptions.PreferDelta,
PreferSpanMetrics: serverOptions.PreferSpanMetrics,
MaxIdleConns: serverOptions.MaxIdleConns,
MaxOpenConns: serverOptions.MaxOpenConns,

View File

@@ -21,24 +21,24 @@ type ModelDao interface {
DB() *sqlx.DB
// auth methods
CanUsePassword(ctx context.Context, email string) (bool, *basemodel.ApiError)
PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr *basemodel.ApiError)
CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError)
PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError)
GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*model.OrgDomain, error)
// org domain (auth domains) CRUD ops
ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, *basemodel.ApiError)
GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, *basemodel.ApiError)
CreateDomain(ctx context.Context, d *model.OrgDomain) *basemodel.ApiError
UpdateDomain(ctx context.Context, domain *model.OrgDomain) *basemodel.ApiError
DeleteDomain(ctx context.Context, id uuid.UUID) *basemodel.ApiError
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, *basemodel.ApiError)
ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, basemodel.BaseApiError)
GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, basemodel.BaseApiError)
CreateDomain(ctx context.Context, d *model.OrgDomain) basemodel.BaseApiError
UpdateDomain(ctx context.Context, domain *model.OrgDomain) basemodel.BaseApiError
DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError)
CreatePAT(ctx context.Context, p model.PAT) (model.PAT, *basemodel.ApiError)
UpdatePAT(ctx context.Context, p model.PAT, id string) *basemodel.ApiError
GetPAT(ctx context.Context, pat string) (*model.PAT, *basemodel.ApiError)
UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) *basemodel.ApiError
GetPATByID(ctx context.Context, id string) (*model.PAT, *basemodel.ApiError)
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, *basemodel.ApiError)
ListPATs(ctx context.Context) ([]model.PAT, *basemodel.ApiError)
RevokePAT(ctx context.Context, id string, userID string) *basemodel.ApiError
CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError)
UpdatePAT(ctx context.Context, p model.PAT, id string) basemodel.BaseApiError
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError)
UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) basemodel.BaseApiError
GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError)
GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError)
ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApiError)
RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError
}

View File

@@ -17,19 +17,19 @@ import (
"go.uber.org/zap"
)
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, *basemodel.ApiError) {
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, basemodel.BaseApiError) {
// get auth domain from email domain
domain, apierr := m.GetDomainByEmail(ctx, email)
if apierr != nil {
zap.L().Error("failed to get domain from email", zap.Error(apierr))
return nil, basemodel.InternalError(fmt.Errorf("failed to get domain from email"))
return nil, model.InternalErrorStr("failed to get domain from email")
}
hash, err := baseauth.PasswordHash(utils.GeneratePassowrd())
if err != nil {
zap.L().Error("failed to generate password hash when registering a user via SSO redirect", zap.Error(err))
return nil, basemodel.InternalError(fmt.Errorf("failed to generate password hash"))
return nil, model.InternalErrorStr("failed to generate password hash")
}
group, apiErr := m.GetGroupByName(ctx, baseconst.ViewerGroup)
@@ -61,12 +61,12 @@ func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (
// PrepareSsoRedirect prepares redirect page link after SSO response
// is successfully parsed (i.e. valid email is available)
func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr *basemodel.ApiError) {
func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError) {
userPayload, apierr := m.GetUserByEmail(ctx, email)
if !apierr.IsNil() {
zap.L().Error("failed to get user with email received from auth provider", zap.String("error", apierr.Error()))
return "", basemodel.BadRequest(fmt.Errorf("invalid user email received from the auth provider"))
return "", model.BadRequestStr("invalid user email received from the auth provider")
}
user := &basemodel.User{}
@@ -85,7 +85,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
tokenStore, err := baseauth.GenerateJWTForUser(user)
if err != nil {
zap.L().Error("failed to generate token for SSO login user", zap.Error(err))
return "", basemodel.InternalError(fmt.Errorf("failed to generate token for the user"))
return "", model.InternalErrorStr("failed to generate token for the user")
}
return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
@@ -95,7 +95,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
tokenStore.RefreshJwt), nil
}
func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, *basemodel.ApiError) {
func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError) {
domain, apierr := m.GetDomainByEmail(ctx, email)
if apierr != nil {
return false, apierr
@@ -110,7 +110,7 @@ func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, *bas
}
if userPayload.Role != baseconst.AdminGroup {
return false, basemodel.BadRequest(fmt.Errorf("auth method not supported"))
return false, model.BadRequest(fmt.Errorf("auth method not supported"))
}
}
@@ -120,7 +120,7 @@ func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, *bas
// PrecheckLogin is called when the login or signup page is loaded
// to check sso login is to be prompted
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*basemodel.PrecheckResponse, *basemodel.ApiError) {
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*basemodel.PrecheckResponse, basemodel.BaseApiError) {
// assume user is valid unless proven otherwise
resp := &basemodel.PrecheckResponse{IsUser: true, CanSelfRegister: false}
@@ -144,7 +144,7 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
ssoAvailable = false
default:
zap.L().Error("feature check failed", zap.String("featureKey", model.SSO), zap.Error(err))
return resp, &basemodel.ApiError{Err: err, Typ: basemodel.ErrorBadData}
return resp, model.BadRequestStr(err.Error())
}
}
@@ -177,7 +177,7 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
siteUrl, err := url.Parse(escapedUrl)
if err != nil {
zap.L().Error("failed to parse referer", zap.Error(err))
return resp, basemodel.InternalError(fmt.Errorf("failed to generate login request"))
return resp, model.InternalError(fmt.Errorf("failed to generate login request"))
}
// build Idp URL that will authenticat the user
@@ -186,7 +186,7 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
if err != nil {
zap.L().Error("failed to prepare saml request for domain", zap.String("domain", orgDomain.Name), zap.Error(err))
return resp, basemodel.InternalError(err)
return resp, model.InternalError(err)
}
// set SSO to true, as the url is generated correctly

View File

@@ -76,47 +76,47 @@ func (m *modelDao) GetDomainFromSsoResponse(ctx context.Context, relayState *url
}
// GetDomainByName returns org domain for a given domain name
func (m *modelDao) GetDomainByName(ctx context.Context, name string) (*model.OrgDomain, *basemodel.ApiError) {
func (m *modelDao) GetDomainByName(ctx context.Context, name string) (*model.OrgDomain, basemodel.BaseApiError) {
stored := StoredDomain{}
err := m.DB().Get(&stored, `SELECT * FROM org_domains WHERE name=$1 LIMIT 1`, name)
if err != nil {
if err == sql.ErrNoRows {
return nil, basemodel.BadRequest(fmt.Errorf("invalid domain name"))
return nil, model.BadRequest(fmt.Errorf("invalid domain name"))
}
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
if err := domain.LoadConfig(stored.Data); err != nil {
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
return domain, nil
}
// GetDomain returns org domain for a given domain id
func (m *modelDao) GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, *basemodel.ApiError) {
func (m *modelDao) GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomain, basemodel.BaseApiError) {
stored := StoredDomain{}
err := m.DB().Get(&stored, `SELECT * FROM org_domains WHERE id=$1 LIMIT 1`, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, basemodel.BadRequest(fmt.Errorf("invalid domain id"))
return nil, model.BadRequest(fmt.Errorf("invalid domain id"))
}
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
if err := domain.LoadConfig(stored.Data); err != nil {
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
return domain, nil
}
// ListDomains gets the list of auth domains by org id
func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, *basemodel.ApiError) {
func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]model.OrgDomain, basemodel.BaseApiError) {
domains := []model.OrgDomain{}
stored := []StoredDomain{}
@@ -126,7 +126,7 @@ func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]model.OrgDo
if err == sql.ErrNoRows {
return []model.OrgDomain{}, nil
}
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
for _, s := range stored {
@@ -141,20 +141,20 @@ func (m *modelDao) ListDomains(ctx context.Context, orgId string) ([]model.OrgDo
}
// CreateDomain creates a new auth domain
func (m *modelDao) CreateDomain(ctx context.Context, domain *model.OrgDomain) *basemodel.ApiError {
func (m *modelDao) CreateDomain(ctx context.Context, domain *model.OrgDomain) basemodel.BaseApiError {
if domain.Id == uuid.Nil {
domain.Id = uuid.New()
}
if domain.OrgId == "" || domain.Name == "" {
return basemodel.BadRequest(fmt.Errorf("domain creation failed, missing fields: OrgId, Name "))
return model.BadRequest(fmt.Errorf("domain creation failed, missing fields: OrgId, Name "))
}
configJson, err := json.Marshal(domain)
if err != nil {
zap.L().Error("failed to unmarshal domain config", zap.Error(err))
return basemodel.InternalError(fmt.Errorf("domain creation failed"))
return model.InternalError(fmt.Errorf("domain creation failed"))
}
_, err = m.DB().ExecContext(ctx,
@@ -168,24 +168,24 @@ func (m *modelDao) CreateDomain(ctx context.Context, domain *model.OrgDomain) *b
if err != nil {
zap.L().Error("failed to insert domain in db", zap.Error(err))
return basemodel.InternalError(fmt.Errorf("domain creation failed"))
return model.InternalError(fmt.Errorf("domain creation failed"))
}
return nil
}
// UpdateDomain updates stored config params for a domain
func (m *modelDao) UpdateDomain(ctx context.Context, domain *model.OrgDomain) *basemodel.ApiError {
func (m *modelDao) UpdateDomain(ctx context.Context, domain *model.OrgDomain) basemodel.BaseApiError {
if domain.Id == uuid.Nil {
zap.L().Error("domain update failed", zap.Error(fmt.Errorf("OrgDomain.Id is null")))
return basemodel.InternalError(fmt.Errorf("domain update failed"))
return model.InternalError(fmt.Errorf("domain update failed"))
}
configJson, err := json.Marshal(domain)
if err != nil {
zap.L().Error("domain update failed", zap.Error(err))
return basemodel.InternalError(fmt.Errorf("domain update failed"))
return model.InternalError(fmt.Errorf("domain update failed"))
}
_, err = m.DB().ExecContext(ctx,
@@ -196,18 +196,18 @@ func (m *modelDao) UpdateDomain(ctx context.Context, domain *model.OrgDomain) *b
if err != nil {
zap.L().Error("domain update failed", zap.Error(err))
return basemodel.InternalError(fmt.Errorf("domain update failed"))
return model.InternalError(fmt.Errorf("domain update failed"))
}
return nil
}
// DeleteDomain deletes an org domain
func (m *modelDao) DeleteDomain(ctx context.Context, id uuid.UUID) *basemodel.ApiError {
func (m *modelDao) DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError {
if id == uuid.Nil {
zap.L().Error("domain delete failed", zap.Error(fmt.Errorf("OrgDomain.Id is null")))
return basemodel.InternalError(fmt.Errorf("domain delete failed"))
return model.InternalError(fmt.Errorf("domain delete failed"))
}
_, err := m.DB().ExecContext(ctx,
@@ -216,21 +216,21 @@ func (m *modelDao) DeleteDomain(ctx context.Context, id uuid.UUID) *basemodel.Ap
if err != nil {
zap.L().Error("domain delete failed", zap.Error(err))
return basemodel.InternalError(fmt.Errorf("domain delete failed"))
return model.InternalError(fmt.Errorf("domain delete failed"))
}
return nil
}
func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, *basemodel.ApiError) {
func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError) {
if email == "" {
return nil, basemodel.BadRequest(fmt.Errorf("could not find auth domain, missing fields: email "))
return nil, model.BadRequest(fmt.Errorf("could not find auth domain, missing fields: email "))
}
components := strings.Split(email, "@")
if len(components) < 2 {
return nil, basemodel.BadRequest(fmt.Errorf("invalid email address"))
return nil, model.BadRequest(fmt.Errorf("invalid email address"))
}
parsedDomain := components[1]
@@ -242,12 +242,12 @@ func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*model.O
if err == sql.ErrNoRows {
return nil, nil
}
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
if err := domain.LoadConfig(stored.Data); err != nil {
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
return domain, nil
}

View File

@@ -11,7 +11,7 @@ import (
"go.uber.org/zap"
)
func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, *basemodel.ApiError) {
func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError) {
result, err := m.DB().ExecContext(ctx,
"INSERT INTO personal_access_tokens (user_id, token, role, name, created_at, expires_at, updated_at, updated_by_user_id, last_used, revoked) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
p.UserID,
@@ -27,12 +27,12 @@ func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, *base
)
if err != nil {
zap.L().Error("Failed to insert PAT in db, err: %v", zap.Error(err))
return model.PAT{}, basemodel.InternalError(fmt.Errorf("PAT insertion failed"))
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
}
id, err := result.LastInsertId()
if err != nil {
zap.L().Error("Failed to get last inserted id, err: %v", zap.Error(err))
return model.PAT{}, basemodel.InternalError(fmt.Errorf("PAT insertion failed"))
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
}
p.Id = strconv.Itoa(int(id))
createdByUser, _ := m.GetUser(ctx, p.UserID)
@@ -53,7 +53,7 @@ func (m *modelDao) CreatePAT(ctx context.Context, p model.PAT) (model.PAT, *base
return p, nil
}
func (m *modelDao) UpdatePAT(ctx context.Context, p model.PAT, id string) *basemodel.ApiError {
func (m *modelDao) UpdatePAT(ctx context.Context, p model.PAT, id string) basemodel.BaseApiError {
_, err := m.DB().ExecContext(ctx,
"UPDATE personal_access_tokens SET role=$1, name=$2, updated_at=$3, updated_by_user_id=$4 WHERE id=$5 and revoked=false;",
p.Role,
@@ -63,29 +63,29 @@ func (m *modelDao) UpdatePAT(ctx context.Context, p model.PAT, id string) *basem
id)
if err != nil {
zap.L().Error("Failed to update PAT in db, err: %v", zap.Error(err))
return basemodel.InternalError(fmt.Errorf("PAT update failed"))
return model.InternalError(fmt.Errorf("PAT update failed"))
}
return nil
}
func (m *modelDao) UpdatePATLastUsed(ctx context.Context, token string, lastUsed int64) *basemodel.ApiError {
func (m *modelDao) UpdatePATLastUsed(ctx context.Context, token string, lastUsed int64) basemodel.BaseApiError {
_, err := m.DB().ExecContext(ctx,
"UPDATE personal_access_tokens SET last_used=$1 WHERE token=$2 and revoked=false;",
lastUsed,
token)
if err != nil {
zap.L().Error("Failed to update PAT last used in db, err: %v", zap.Error(err))
return basemodel.InternalError(fmt.Errorf("PAT last used update failed"))
return model.InternalError(fmt.Errorf("PAT last used update failed"))
}
return nil
}
func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, *basemodel.ApiError) {
func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, basemodel.BaseApiError) {
pats := []model.PAT{}
if err := m.DB().Select(&pats, "SELECT * FROM personal_access_tokens WHERE revoked=false ORDER by updated_at DESC;"); err != nil {
zap.L().Error("Failed to fetch PATs err: %v", zap.Error(err))
return nil, basemodel.InternalError(fmt.Errorf("failed to fetch PATs"))
return nil, model.InternalError(fmt.Errorf("failed to fetch PATs"))
}
for i := range pats {
createdByUser, _ := m.GetUser(ctx, pats[i].UserID)
@@ -123,28 +123,28 @@ func (m *modelDao) ListPATs(ctx context.Context) ([]model.PAT, *basemodel.ApiErr
return pats, nil
}
func (m *modelDao) RevokePAT(ctx context.Context, id string, userID string) *basemodel.ApiError {
func (m *modelDao) RevokePAT(ctx context.Context, id string, userID string) basemodel.BaseApiError {
updatedAt := time.Now().Unix()
_, err := m.DB().ExecContext(ctx,
"UPDATE personal_access_tokens SET revoked=true, updated_by_user_id = $1, updated_at=$2 WHERE id=$3",
userID, updatedAt, id)
if err != nil {
zap.L().Error("Failed to revoke PAT in db, err: %v", zap.Error(err))
return basemodel.InternalError(fmt.Errorf("PAT revoke failed"))
return model.InternalError(fmt.Errorf("PAT revoke failed"))
}
return nil
}
func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, *basemodel.ApiError) {
func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemodel.BaseApiError) {
pats := []model.PAT{}
if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE token=? and revoked=false;`, token); err != nil {
return nil, basemodel.InternalError(fmt.Errorf("failed to fetch PAT"))
return nil, model.InternalError(fmt.Errorf("failed to fetch PAT"))
}
if len(pats) != 1 {
return nil, &basemodel.ApiError{
Typ: basemodel.ErrorInternal,
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: fmt.Errorf("found zero or multiple PATs with same token, %s", token),
}
}
@@ -152,16 +152,16 @@ func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, *basem
return &pats[0], nil
}
func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, *basemodel.ApiError) {
func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError) {
pats := []model.PAT{}
if err := m.DB().Select(&pats, `SELECT * FROM personal_access_tokens WHERE id=? and revoked=false;`, id); err != nil {
return nil, basemodel.InternalError(fmt.Errorf("failed to fetch PAT"))
return nil, model.InternalError(fmt.Errorf("failed to fetch PAT"))
}
if len(pats) != 1 {
return nil, &basemodel.ApiError{
Typ: basemodel.ErrorInternal,
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: fmt.Errorf("found zero or multiple PATs with same token"),
}
}
@@ -170,7 +170,7 @@ func (m *modelDao) GetPATByID(ctx context.Context, id string) (*model.PAT, *base
}
// deprecated
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, *basemodel.ApiError) {
func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.UserPayload, basemodel.BaseApiError) {
users := []basemodel.UserPayload{}
query := `SELECT
@@ -186,12 +186,12 @@ func (m *modelDao) GetUserByPAT(ctx context.Context, token string) (*basemodel.U
WHERE u.id = p.user_id and p.token=? and p.expires_at >= strftime('%s', 'now');`
if err := m.DB().Select(&users, query, token); err != nil {
return nil, basemodel.InternalError(fmt.Errorf("failed to fetch user from PAT, err: %v", err))
return nil, model.InternalError(fmt.Errorf("failed to fetch user from PAT, err: %v", err))
}
if len(users) != 1 {
return nil, &basemodel.ApiError{
Typ: basemodel.ErrorInternal,
return nil, &model.ApiError{
Typ: model.ErrorInternal,
Err: fmt.Errorf("found zero or multiple users with same PAT token"),
}
}

View File

@@ -13,7 +13,6 @@ import (
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
var C *Client
@@ -38,7 +37,7 @@ func init() {
}
// ActivateLicense sends key to license.signoz.io and gets activation data
func ActivateLicense(key, siteId string) (*ActivationResponse, *basemodel.ApiError) {
func ActivateLicense(key, siteId string) (*ActivationResponse, *model.ApiError) {
licenseReq := map[string]string{
"key": key,
"siteId": siteId,
@@ -49,13 +48,13 @@ func ActivateLicense(key, siteId string) (*ActivationResponse, *basemodel.ApiErr
if err != nil {
zap.L().Error("failed to connect to license.signoz.io", zap.Error(err))
return nil, basemodel.BadRequest(fmt.Errorf("unable to connect with license.signoz.io, please check your network connection"))
return nil, model.BadRequest(fmt.Errorf("unable to connect with license.signoz.io, please check your network connection"))
}
httpBody, err := io.ReadAll(httpResponse.Body)
if err != nil {
zap.L().Error("failed to read activation response from license.signoz.io", zap.Error(err))
return nil, basemodel.BadRequest(fmt.Errorf("failed to read activation response from license.signoz.io"))
return nil, model.BadRequest(fmt.Errorf("failed to read activation response from license.signoz.io"))
}
defer httpResponse.Body.Close()
@@ -65,22 +64,22 @@ func ActivateLicense(key, siteId string) (*ActivationResponse, *basemodel.ApiErr
err = json.Unmarshal(httpBody, &result)
if err != nil {
zap.L().Error("failed to marshal activation response from license.signoz.io", zap.Error(err))
return nil, basemodel.InternalError(errors.Wrap(err, "failed to marshal license activation response"))
return nil, model.InternalError(errors.Wrap(err, "failed to marshal license activation response"))
}
switch httpResponse.StatusCode {
case 200, 201:
return result.Data, nil
case 400, 401:
return nil, basemodel.BadRequest(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
return nil, model.BadRequest(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
default:
return nil, basemodel.InternalError(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
return nil, model.InternalError(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
}
}
// ValidateLicense validates the license key
func ValidateLicense(activationId string) (*ActivationResponse, *basemodel.ApiError) {
func ValidateLicense(activationId string) (*ActivationResponse, *model.ApiError) {
validReq := map[string]string{
"activationId": activationId,
}
@@ -89,12 +88,12 @@ func ValidateLicense(activationId string) (*ActivationResponse, *basemodel.ApiEr
response, err := http.Post(C.Prefix+"/licenses/validate", APPLICATION_JSON, bytes.NewBuffer(reqString))
if err != nil {
return nil, basemodel.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
return nil, model.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, basemodel.BadRequest(errors.Wrap(err, "failed to read validation response from license.signoz.io"))
return nil, model.BadRequest(errors.Wrap(err, "failed to read validation response from license.signoz.io"))
}
defer response.Body.Close()
@@ -104,14 +103,14 @@ func ValidateLicense(activationId string) (*ActivationResponse, *basemodel.ApiEr
a := ActivationResult{}
err = json.Unmarshal(body, &a)
if err != nil {
return nil, basemodel.BadRequest(errors.Wrap(err, "failed to marshal license validation response"))
return nil, model.BadRequest(errors.Wrap(err, "failed to marshal license validation response"))
}
return a.Data, nil
case 400, 401:
return nil, basemodel.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
return nil, model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
"bad request error received from license.signoz.io"))
default:
return nil, basemodel.InternalError(errors.Wrap(fmt.Errorf(string(body)),
return nil, model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
"internal error received from license.signoz.io"))
}
@@ -128,21 +127,21 @@ func NewPostRequestWithCtx(ctx context.Context, url string, contentType string,
}
// SendUsage reports the usage of signoz to license server
func SendUsage(ctx context.Context, usage model.UsagePayload) *basemodel.ApiError {
func SendUsage(ctx context.Context, usage model.UsagePayload) *model.ApiError {
reqString, _ := json.Marshal(usage)
req, err := NewPostRequestWithCtx(ctx, C.Prefix+"/usage", APPLICATION_JSON, bytes.NewBuffer(reqString))
if err != nil {
return basemodel.BadRequest(errors.Wrap(err, "unable to create http request"))
return model.BadRequest(errors.Wrap(err, "unable to create http request"))
}
res, err := http.DefaultClient.Do(req)
if err != nil {
return basemodel.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
return model.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
}
body, err := io.ReadAll(res.Body)
if err != nil {
return basemodel.BadRequest(errors.Wrap(err, "failed to read usage response from license.signoz.io"))
return model.BadRequest(errors.Wrap(err, "failed to read usage response from license.signoz.io"))
}
defer res.Body.Close()
@@ -151,10 +150,10 @@ func SendUsage(ctx context.Context, usage model.UsagePayload) *basemodel.ApiErro
case 200, 201:
return nil
case 400, 401:
return basemodel.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
return model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
"bad request error received from license.signoz.io"))
default:
return basemodel.InternalError(errors.Wrap(fmt.Errorf(string(body)),
return model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
"internal error received from license.signoz.io"))
}
}

View File

@@ -137,11 +137,11 @@ func (lm *Manager) LoadActiveLicense(features ...basemodel.Feature) error {
return nil
}
func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, apiError *basemodel.ApiError) {
func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, apiError *model.ApiError) {
licenses, err := lm.repo.GetLicenses(ctx)
if err != nil {
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
for _, l := range licenses {
@@ -212,8 +212,8 @@ func (lm *Manager) Validate(ctx context.Context) (reterr error) {
response, apiError := validate.ValidateLicense(lm.activeLicense.ActivationId)
if apiError != nil {
zap.L().Error("failed to validate license", zap.Any("apiError", apiError))
return apiError
zap.L().Error("failed to validate license", zap.Error(apiError.Err))
return apiError.Err
}
if response.PlanDetails == lm.activeLicense.PlanDetails {
@@ -255,7 +255,7 @@ func (lm *Manager) Validate(ctx context.Context) (reterr error) {
}
// Activate activates a license key with signoz server
func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *model.License, errResponse *basemodel.ApiError) {
func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *model.License, errResponse *model.ApiError) {
defer func() {
if errResponse != nil {
userEmail, err := auth.GetEmailFromJwt(ctx)
@@ -268,7 +268,7 @@ func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *m
response, apiError := validate.ActivateLicense(key, "")
if apiError != nil {
zap.L().Error("failed to activate license", zap.Any("apiError", apiError))
zap.L().Error("failed to activate license", zap.Error(apiError.Err))
return nil, apiError
}
@@ -283,14 +283,14 @@ func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *m
if err != nil {
zap.L().Error("failed to activate license", zap.Error(err))
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
// store the license before activating it
err = lm.repo.InsertLicense(ctx, l)
if err != nil {
zap.L().Error("failed to activate license", zap.Error(err))
return nil, basemodel.InternalError(err)
return nil, model.InternalError(err)
}
// license is valid, activate it

View File

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

View File

@@ -1,5 +1,107 @@
package model
import (
"fmt"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
type ApiError struct {
Typ basemodel.ErrorType
Err error
}
func (a *ApiError) Type() basemodel.ErrorType {
return a.Typ
}
func (a *ApiError) ToError() error {
if a != nil {
return a.Err
}
return a.Err
}
func (a *ApiError) Error() string {
return a.Err.Error()
}
func (a *ApiError) IsNil() bool {
return a == nil || a.Err == nil
}
// NewApiError returns a ApiError object of given type
func NewApiError(typ basemodel.ErrorType, err error) *ApiError {
return &ApiError{
Typ: typ,
Err: err,
}
}
// BadRequest returns a ApiError object of bad request
func BadRequest(err error) *ApiError {
return &ApiError{
Typ: basemodel.ErrorBadData,
Err: err,
}
}
// BadRequestStr returns a ApiError object of bad request for string input
func BadRequestStr(s string) *ApiError {
return &ApiError{
Typ: basemodel.ErrorBadData,
Err: fmt.Errorf(s),
}
}
// InternalError returns a ApiError object of internal type
func InternalError(err error) *ApiError {
return &ApiError{
Typ: basemodel.ErrorInternal,
Err: err,
}
}
// InternalErrorStr returns a ApiError object of internal type for string input
func InternalErrorStr(s string) *ApiError {
return &ApiError{
Typ: basemodel.ErrorInternal,
Err: fmt.Errorf(s),
}
}
var (
ErrorNone basemodel.ErrorType = ""
ErrorTimeout basemodel.ErrorType = "timeout"
ErrorCanceled basemodel.ErrorType = "canceled"
ErrorExec basemodel.ErrorType = "execution"
ErrorBadData basemodel.ErrorType = "bad_data"
ErrorInternal basemodel.ErrorType = "internal"
ErrorUnavailable basemodel.ErrorType = "unavailable"
ErrorNotFound basemodel.ErrorType = "not_found"
ErrorNotImplemented basemodel.ErrorType = "not_implemented"
ErrorUnauthorized basemodel.ErrorType = "unauthorized"
ErrorForbidden basemodel.ErrorType = "forbidden"
ErrorConflict basemodel.ErrorType = "conflict"
ErrorStreamingNotSupported basemodel.ErrorType = "streaming is not supported"
)
func init() {
ErrorNone = basemodel.ErrorNone
ErrorTimeout = basemodel.ErrorTimeout
ErrorCanceled = basemodel.ErrorCanceled
ErrorExec = basemodel.ErrorExec
ErrorBadData = basemodel.ErrorBadData
ErrorInternal = basemodel.ErrorInternal
ErrorUnavailable = basemodel.ErrorUnavailable
ErrorNotFound = basemodel.ErrorNotFound
ErrorNotImplemented = basemodel.ErrorNotImplemented
ErrorUnauthorized = basemodel.ErrorUnauthorized
ErrorForbidden = basemodel.ErrorForbidden
ErrorConflict = basemodel.ErrorConflict
ErrorStreamingNotSupported = basemodel.ErrorStreamingNotSupported
}
type ErrUnsupportedAuth struct{}
func (errUnsupportedAuth ErrUnsupportedAuth) Error() string {

View File

@@ -190,7 +190,7 @@ func (lm *Manager) UploadUsageWithExponentalBackOff(ctx context.Context, payload
} else if apiErr != nil {
// sleeping for exponential backoff
sleepDuration := RetryInterval * time.Duration(i)
zap.L().Error("failed to upload snapshot retrying after %v secs : %v", zap.Duration("sleepDuration", sleepDuration), zap.Any("apiErr", apiErr))
zap.L().Error("failed to upload snapshot retrying after %v secs : %v", zap.Duration("sleepDuration", sleepDuration), zap.Error(apiErr.Err))
time.Sleep(sleepDuration)
} else {
break

View File

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

View File

@@ -88,6 +88,7 @@
"lucide-react": "0.379.0",
"mini-css-extract-plugin": "2.4.5",
"papaparse": "5.4.1",
"posthog-js": "1.142.1",
"rc-tween-one": "3.0.6",
"react": "18.2.0",
"react-addons-update": "15.6.3",

View File

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

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

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

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

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

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

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

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

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

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

After

Width:  |  Height:  |  Size: 973 B

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,6 +17,7 @@ import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import { identity, pick, pickBy } from 'lodash-es';
import posthog from 'posthog-js';
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useEffect, useState } from 'react';
@@ -38,7 +39,7 @@ import defaultRoutes, {
function App(): JSX.Element {
const themeConfig = useThemeConfig();
const { data } = useLicense();
const { data: licenseData } = useLicense();
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
const { role, isLoggedIn: isLoggedInState, user, org } = useSelector<
AppState,
@@ -92,10 +93,10 @@ function App(): JSX.Element {
});
const isOnBasicPlan =
data?.payload?.licenses?.some(
licenseData?.payload?.licenses?.some(
(license) =>
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
) || data?.payload?.licenses === null;
) || licenseData?.payload?.licenses === null;
const enableAnalytics = (user: User): void => {
const orgName =
@@ -112,9 +113,7 @@ function App(): JSX.Element {
};
const sanitizedIdentifyPayload = pickBy(identifyPayload, identity);
const domain = extractDomain(email);
const hostNameParts = hostname.split('.');
const groupTraits = {
@@ -127,10 +126,30 @@ function App(): JSX.Element {
};
window.analytics.identify(email, sanitizedIdentifyPayload);
window.analytics.group(domain, groupTraits);
window.clarity('identify', email, name);
posthog?.identify(email, {
email,
name,
orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
tenant_url: hostname,
company_domain: domain,
source: 'signoz-ui',
isPaidUser: !!licenseData?.payload?.trialConvertedToSubscription,
});
posthog?.group('company', domain, {
name: orgName,
tenant_id: hostNameParts[0],
data_region: hostNameParts[1],
tenant_url: hostname,
company_domain: domain,
source: 'signoz-ui',
isPaidUser: !!licenseData?.payload?.trialConvertedToSubscription,
});
};
useEffect(() => {
@@ -144,10 +163,6 @@ function App(): JSX.Element {
!isIdentifiedUser
) {
setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
if (isCloudUserVal) {
enableAnalytics(user);
}
}
if (
@@ -195,6 +210,11 @@ function App(): JSX.Element {
console.error('Failed to parse local storage theme analytics event');
}
}
if (isCloudUserVal && user && user.email) {
enableAnalytics(user);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user]);

View File

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

View File

@@ -3,8 +3,15 @@ import './DropDown.styles.scss';
import { EllipsisOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useState } from 'react';
function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
function DropDown({
element,
onDropDownItemClick,
}: {
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
const isDarkMode = useIsDarkMode();
const items: MenuProps['items'] = element.map(
@@ -14,12 +21,25 @@ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
}),
);
const [isDdOpen, setDdOpen] = useState<boolean>(false);
return (
<Dropdown menu={{ items }}>
<Dropdown
menu={{
items,
onMouseEnter: (): void => setDdOpen(true),
onMouseLeave: (): void => setDdOpen(false),
onClick: (item): void => onDropDownItemClick?.(item),
}}
open={isDdOpen}
>
<Button
type="link"
className={!isDarkMode ? 'dropdown-button--dark' : 'dropdown-button'}
onClick={(e): void => e.preventDefault()}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);
}}
>
<EllipsisOutlined className="dropdown-icon" />
</Button>
@@ -27,4 +47,8 @@ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
);
}
DropDown.defaultProps = {
onDropDownItemClick: (): void => {},
};
export default DropDown;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './Uplot.styles.scss';
import * as Sentry from '@sentry/react';
import { Typography } from 'antd';
import { ToggleGraphProps } from 'components/Graph/types';
import { LineChart } from 'lucide-react';
@@ -13,7 +14,6 @@ import {
useImperativeHandle,
useRef,
} from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import UPlot from 'uplot';
import { dataMatch, optionsUpdateState } from './utils';
@@ -139,7 +139,7 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
}
return (
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<div className="uplot-graph-container" ref={targetRef}>
{data && data[0] && data[0]?.length === 0 ? (
<div className="not-found">
@@ -147,7 +147,7 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
</div>
) : null}
</div>
</ErrorBoundary>
</Sentry.ErrorBoundary>
);
},
);

View File

@@ -40,4 +40,5 @@ export const getComponentForPanelType = (
export const AVAILABLE_EXPORT_PANEL_TYPES = [
PANEL_TYPES.TIME_SERIES,
PANEL_TYPES.TABLE,
PANEL_TYPES.LIST,
];

View File

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

View File

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

View File

@@ -3,9 +3,9 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import './AppLayout.styles.scss';
import * as Sentry from '@sentry/react';
import { Flex } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion';
import cx from 'classnames';
@@ -27,7 +27,6 @@ import {
useRef,
useState,
} from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
@@ -38,7 +37,6 @@ import { sideBarCollapse } from 'store/actions';
import { AppState } from 'store/reducers';
import AppActions from 'types/actions';
import {
UPDATE_CONFIGS,
UPDATE_CURRENT_ERROR,
UPDATE_CURRENT_VERSION,
UPDATE_LATEST_VERSION,
@@ -66,11 +64,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const { pathname } = useLocation();
const { t } = useTranslation(['titles']);
const [
getUserVersionResponse,
getUserLatestVersionResponse,
getDynamicConfigsResponse,
] = useQueries([
const [getUserVersionResponse, getUserLatestVersionResponse] = useQueries([
{
queryFn: getUserVersion,
queryKey: ['getUserVersion', user?.accessJwt],
@@ -81,10 +75,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
queryKey: ['getUserLatestVersion', user?.accessJwt],
enabled: isLoggedIn,
},
{
queryFn: getDynamicConfigs,
queryKey: ['getDynamicConfigs', user?.accessJwt],
},
]);
useEffect(() => {
@@ -95,15 +85,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
if (getUserVersionResponse.status === 'idle' && isLoggedIn) {
getUserVersionResponse.refetch();
}
if (getDynamicConfigsResponse.status === 'idle') {
getDynamicConfigsResponse.refetch();
}
}, [
getUserLatestVersionResponse,
getUserVersionResponse,
isLoggedIn,
getDynamicConfigsResponse,
]);
}, [getUserLatestVersionResponse, getUserVersionResponse, isLoggedIn]);
const { children } = props;
@@ -111,7 +93,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const latestCurrentCounter = useRef(0);
const latestVersionCounter = useRef(0);
const latestConfigCounter = useRef(0);
const { notifications } = useNotifications();
@@ -189,23 +170,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
},
});
}
if (
getDynamicConfigsResponse.isFetched &&
getDynamicConfigsResponse.isSuccess &&
getDynamicConfigsResponse.data &&
getDynamicConfigsResponse.data.payload &&
latestConfigCounter.current === 0
) {
latestConfigCounter.current = 1;
dispatch({
type: UPDATE_CONFIGS,
payload: {
configs: getDynamicConfigsResponse.data.payload,
},
});
}
}, [
dispatch,
isLoggedIn,
@@ -220,9 +184,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
getUserLatestVersionResponse.isFetched,
getUserVersionResponse.isFetched,
getUserLatestVersionResponse.isSuccess,
getDynamicConfigsResponse.data,
getDynamicConfigsResponse.isFetched,
getDynamicConfigsResponse.isSuccess,
notifications,
]);
@@ -236,7 +197,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING ||
pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING ||
pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT ||
pathname === ROUTES.GET_STARTED_AWS_MONITORING;
pathname === ROUTES.GET_STARTED_AWS_MONITORING ||
pathname === ROUTES.GET_STARTED_AZURE_MONITORING;
const [showTrialExpiryBanner, setShowTrialExpiryBanner] = useState(false);
@@ -342,7 +304,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
/>
)}
<div className={cx('app-content', collapsed ? 'collapsed' : '')}>
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<LayoutContent>
<ChildrenContainer
style={{
@@ -360,7 +322,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
{children}
</ChildrenContainer>
</LayoutContent>
</ErrorBoundary>
</Sentry.ErrorBoundary>
</div>
</Flex>
</Layout>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -246,17 +246,19 @@ function ChartPreview({
return (
<ChartContainer>
{headline}
{(queryResponse?.isError || queryResponse?.error) && (
<FailedMessageContainer color="red" title="Failed to refresh the chart">
<InfoCircleOutlined />{' '}
{queryResponse.error.message || t('preview_chart_unexpected_error')}
</FailedMessageContainer>
)}
{chartData && !queryResponse.isError && (
<div ref={graphRef} style={{ height: '100%' }}>
{queryResponse.isLoading && (
<Spinner size="large" tip="Loading..." height="100%" />
)}
<div ref={graphRef} style={{ height: '100%' }}>
{queryResponse.isLoading && (
<Spinner size="large" tip="Loading..." height="100%" />
)}
{(queryResponse?.isError || queryResponse?.error) && (
<FailedMessageContainer color="red" title="Failed to refresh the chart">
<InfoCircleOutlined />{' '}
{queryResponse.error.message || t('preview_chart_unexpected_error')}
</FailedMessageContainer>
)}
{chartData && !queryResponse.isError && (
<GridPanelSwitch
options={options}
panelType={graphType}
@@ -268,8 +270,8 @@ function ChartPreview({
query={query || initialQueriesMap.metrics}
yAxisUnit={yAxisUnit}
/>
</div>
)}
)}
</div>
</ChartContainer>
);
}

View File

@@ -2,6 +2,7 @@ import './QuerySection.styles.scss';
import { Color } from '@signozhq/design-tokens';
import { Button, Tabs, Tooltip } from 'antd';
import logEvent from 'api/common/logEvent';
import PromQLIcon from 'assets/Dashboard/PromQl';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { ENTITY_VERSION_V4 } from 'constants/app';
@@ -31,6 +32,7 @@ function QuerySection({
runQuery,
alertDef,
panelType,
ruleId,
}: QuerySectionProps): JSX.Element {
// init namespace for translations
const { t } = useTranslation('alerts');
@@ -158,7 +160,15 @@ function QuerySection({
<span style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
<Button
type="primary"
onClick={runQuery}
onClick={(): void => {
runQuery();
logEvent('Alert: Stage and run query', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertType],
isNewRule: !ruleId || ruleId === 0,
ruleId,
queryType: queryCategory,
});
}}
className="stage-run-query"
icon={<Play size={14} />}
>
@@ -228,6 +238,7 @@ interface QuerySectionProps {
runQuery: VoidFunction;
alertDef: AlertDef;
panelType: PANEL_TYPES;
ruleId: number;
}
export default QuerySection;

View File

@@ -12,8 +12,10 @@ import {
} from 'antd';
import saveAlertApi from 'api/alerts/save';
import testAlertApi from 'api/alerts/testAlert';
import logEvent from 'api/common/logEvent';
import FacingIssueBtn from 'components/facingIssueBtn/FacingIssueBtn';
import { alertHelpMessage } from 'components/facingIssueBtn/util';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { FeatureKeys } from 'constants/features';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
@@ -77,7 +79,8 @@ function FormAlertRules({
const urlQuery = useUrlQuery();
const panelType = urlQuery.get(QueryParams.panelTypes) as PANEL_TYPES | null;
// In case of alert the panel types should always be "Graph" only
const panelType = PANEL_TYPES.TIME_SERIES;
const {
currentQuery,
@@ -337,8 +340,13 @@ function FormAlertRules({
return;
}
const postableAlert = memoizedPreparePostData();
setLoading(true);
let logData = {
status: 'error',
statusMessage: t('unexpected_error'),
};
try {
const apiReq =
ruleId && ruleId > 0
@@ -348,10 +356,15 @@ function FormAlertRules({
const response = await saveAlertApi(apiReq);
if (response.statusCode === 200) {
logData = {
status: 'success',
statusMessage:
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
};
notifications.success({
message: 'Success',
description:
!ruleId || ruleId === 0 ? t('rule_created') : t('rule_edited'),
description: logData.statusMessage,
});
// invalidate rule in cache
@@ -366,18 +379,42 @@ function FormAlertRules({
history.replace(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
}, 2000);
} else {
logData = {
status: 'error',
statusMessage: response.error || t('unexpected_error'),
};
notifications.error({
message: 'Error',
description: response.error || t('unexpected_error'),
description: logData.statusMessage,
});
}
} catch (e) {
logData = {
status: 'error',
statusMessage: t('unexpected_error'),
};
notifications.error({
message: 'Error',
description: t('unexpected_error'),
description: logData.statusMessage,
});
}
setLoading(false);
logEvent('Alert: Save alert', {
...logData,
dataSource: ALERTS_DATA_SOURCE_MAP[postableAlert?.alertType as AlertTypes],
channelNames: postableAlert?.preferredChannels,
broadcastToAll: postableAlert?.broadcastToAll,
isNewRule: !ruleId || ruleId === 0,
ruleId,
queryType: currentQuery.queryType,
alertId: postableAlert?.id,
alertName: postableAlert?.alert,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
isFormValid,
memoizedPreparePostData,
@@ -413,6 +450,7 @@ function FormAlertRules({
}
const postableAlert = memoizedPreparePostData();
let statusResponse = { status: 'failed', message: '' };
setLoading(true);
try {
const response = await testAlertApi({ data: postableAlert });
@@ -424,25 +462,43 @@ function FormAlertRules({
message: 'Error',
description: t('no_alerts_found'),
});
statusResponse = { status: 'failed', message: t('no_alerts_found') };
} else {
notifications.success({
message: 'Success',
description: t('rule_test_fired'),
});
statusResponse = { status: 'success', message: t('rule_test_fired') };
}
} else {
notifications.error({
message: 'Error',
description: response.error || t('unexpected_error'),
});
statusResponse = {
status: 'failed',
message: response.error || t('unexpected_error'),
};
}
} catch (e) {
notifications.error({
message: 'Error',
description: t('unexpected_error'),
});
statusResponse = { status: 'failed', message: t('unexpected_error') };
}
setLoading(false);
logEvent('Alert: Test notification', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
channelNames: postableAlert?.preferredChannels,
broadcastToAll: postableAlert?.broadcastToAll,
isNewRule: !ruleId || ruleId === 0,
ruleId,
queryType: currentQuery.queryType,
status: statusResponse.status,
statusMessage: statusResponse.message,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [t, isFormValid, memoizedPreparePostData, notifications]);
const renderBasicInfo = (): JSX.Element => (
@@ -512,6 +568,16 @@ function FormAlertRules({
const isRuleCreated = !ruleId || ruleId === 0;
useEffect(() => {
if (!isRuleCreated) {
logEvent('Alert: Edit page visited', {
ruleId,
dataSource: ALERTS_DATA_SOURCE_MAP[alertType as AlertTypes],
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
function handleRedirection(option: AlertTypes): void {
let url = '';
switch (option) {
@@ -534,6 +600,13 @@ function FormAlertRules({
default:
break;
}
logEvent('Alert: Check example alert clicked', {
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
isNewRule: !ruleId || ruleId === 0,
ruleId,
queryType: currentQuery.queryType,
link: url,
});
window.open(url, '_blank');
}
@@ -571,6 +644,7 @@ function FormAlertRules({
alertDef={alertDef}
panelType={panelType || PANEL_TYPES.TIME_SERIES}
key={currentQuery.queryType}
ruleId={ruleId}
/>
<RuleOptions

View File

@@ -80,6 +80,8 @@ function FullView({
query: updatedQuery,
globalSelectedInterval: globalSelectedTime,
variables: getDashboardVariables(selectedDashboard?.data.variables),
fillGaps: widget.fillSpans,
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
};
}
updatedQuery.builder.queryData[0].pageSize = 10;

View File

@@ -108,6 +108,8 @@ function GridCardGraph({
query: updatedQuery,
globalSelectedInterval,
variables: getDashboardVariables(variables),
fillGaps: widget.fillSpans,
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
};
}
updatedQuery.builder.queryData[0].pageSize = 10;
@@ -122,6 +124,7 @@ function GridCardGraph({
limit: updatedQuery.builder.queryData[0].limit || 0,
},
},
fillGaps: widget.fillSpans,
};
});
@@ -152,6 +155,7 @@ function GridCardGraph({
widget?.query,
widget?.panelTypes,
widget.timePreferance,
widget.fillSpans,
requestData,
],
retry(failureCount, error): boolean {

View File

@@ -0,0 +1,215 @@
export const tableDataMultipleQueriesSuccessResponse = {
columns: [
{
name: 'service_name',
queryName: '',
isValueColumn: false,
},
{
name: 'A',
queryName: 'A',
isValueColumn: true,
},
{
name: 'B',
queryName: 'B',
isValueColumn: true,
},
],
rows: [
{
data: {
A: 4196.71,
B: 'n/a',
service_name: 'demo-app',
},
},
{
data: {
A: 500.83,
B: 'n/a',
service_name: 'customer',
},
},
{
data: {
A: 499.5,
B: 'n/a',
service_name: 'mysql',
},
},
{
data: {
A: 293.22,
B: 'n/a',
service_name: 'frontend',
},
},
{
data: {
A: 230.03,
B: 'n/a',
service_name: 'driver',
},
},
{
data: {
A: 67.09,
B: 'n/a',
service_name: 'route',
},
},
{
data: {
A: 30.96,
B: 'n/a',
service_name: 'redis',
},
},
{
data: {
A: 'n/a',
B: 112.27,
service_name: 'n/a',
},
},
],
};
export const widgetQueryWithLegend = {
clickhouse_sql: [
{
name: 'A',
legend: '',
disabled: false,
query: '',
},
],
promql: [
{
name: 'A',
query: '',
legend: '',
disabled: false,
},
],
builder: {
queryData: [
{
dataSource: 'metrics',
queryName: 'A',
aggregateOperator: 'count',
aggregateAttribute: {
dataType: 'float64',
id: 'signoz_latency--float64--ExponentialHistogram--true',
isColumn: true,
isJSON: false,
key: 'signoz_latency',
type: 'ExponentialHistogram',
},
timeAggregation: '',
spaceAggregation: 'p90',
functions: [],
filters: {
items: [],
op: 'AND',
},
expression: 'A',
disabled: false,
stepInterval: 60,
having: [],
limit: null,
orderBy: [],
groupBy: [
{
dataType: 'string',
isColumn: false,
isJSON: false,
key: 'service_name',
type: 'tag',
id: 'service_name--string--tag--false',
},
],
legend: 'p99',
reduceTo: 'avg',
},
{
dataSource: 'metrics',
queryName: 'B',
aggregateOperator: 'rate',
aggregateAttribute: {
dataType: 'float64',
id: 'system_disk_operations--float64--Sum--true',
isColumn: true,
isJSON: false,
key: 'system_disk_operations',
type: 'Sum',
},
timeAggregation: 'rate',
spaceAggregation: 'sum',
functions: [],
filters: {
items: [],
op: 'AND',
},
expression: 'B',
disabled: false,
stepInterval: 60,
having: [],
limit: null,
orderBy: [],
groupBy: [],
legend: '',
reduceTo: 'avg',
},
],
queryFormulas: [],
},
id: '48ad5a67-9a3c-49d4-a886-d7a34f8b875d',
queryType: 'builder',
};
export const expectedOutputWithLegends = {
dataSource: [
{
A: 4196.71,
B: 'n/a',
service_name: 'demo-app',
},
{
A: 500.83,
B: 'n/a',
service_name: 'customer',
},
{
A: 499.5,
B: 'n/a',
service_name: 'mysql',
},
{
A: 293.22,
B: 'n/a',
service_name: 'frontend',
},
{
A: 230.03,
B: 'n/a',
service_name: 'driver',
},
{
A: 67.09,
B: 'n/a',
service_name: 'route',
},
{
A: 30.96,
B: 'n/a',
service_name: 'redis',
},
{
A: 'n/a',
B: 112.27,
service_name: 'n/a',
},
],
};

View File

@@ -0,0 +1,42 @@
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { createColumnsAndDataSource, getQueryLegend } from '../utils';
import {
expectedOutputWithLegends,
tableDataMultipleQueriesSuccessResponse,
widgetQueryWithLegend,
} from './response';
describe('Table Panel utils', () => {
it('createColumnsAndDataSource function', () => {
const data = tableDataMultipleQueriesSuccessResponse;
const query = widgetQueryWithLegend as Query;
const { columns, dataSource } = createColumnsAndDataSource(data, query);
expect(dataSource).toStrictEqual(expectedOutputWithLegends.dataSource);
// this makes sure that the columns are rendered in the same order as response
expect(columns[0].title).toBe('service_name');
// the next specifically makes sure that the legends are properly applied in multiple queries
expect(columns[1].title).toBe('p99');
// this makes sure that the query without a legend takes the title from the query response
expect(columns[2].title).toBe('B');
// this is to ensure that the rows properly map to the column data indexes as the dataIndex should be equal to name of the columns
// returned in the response as the rows will be mapped with them
expect((columns[0] as any).dataIndex).toBe('service_name');
expect((columns[1] as any).dataIndex).toBe('A');
expect((columns[2] as any).dataIndex).toBe('B');
});
it('getQueryLegend function', () => {
const query = widgetQueryWithLegend as Query;
// query A has a legend of p99
expect(getQueryLegend(query, 'A')).toBe('p99');
// should return undefined when legend not present
expect(getQueryLegend(query, 'B')).toBe(undefined);
});
});

View File

@@ -3,10 +3,7 @@ import { Space, Tooltip } from 'antd';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import { Events } from 'constants/events';
import { QueryTable } from 'container/QueryTable';
import {
createTableColumnsFromQuery,
RowData,
} from 'lib/query/createTableColumnsFromQuery';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { cloneDeep, get, isEmpty, set } from 'lodash-es';
import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -14,7 +11,11 @@ import { eventEmitter } from 'utils/getEventEmitter';
import { WrapperStyled } from './styles';
import { GridTableComponentProps } from './types';
import { findMatchingThreshold } from './utils';
import {
createColumnsAndDataSource,
findMatchingThreshold,
TableData,
} from './utils';
function GridTableComponent({
data,
@@ -25,28 +26,26 @@ function GridTableComponent({
...props
}: GridTableComponentProps): JSX.Element {
const { t } = useTranslation(['valueGraph']);
// create columns and dataSource in the ui friendly structure
// use the query from the widget here to extract the legend information
const { columns, dataSource: originalDataSource } = useMemo(
() =>
createTableColumnsFromQuery({
query,
queryTableData: data,
}),
[data, query],
() => createColumnsAndDataSource((data as unknown) as TableData, query),
[query, data],
);
const createDataInCorrectFormat = useCallback(
(dataSource: RowData[]): RowData[] =>
dataSource.map((d) => {
const finalObject = {};
const keys = Object.keys(d);
keys.forEach((k) => {
const label = get(
columns.find((c) => get(c, 'dataIndex', '') === k) || {},
'title',
'',
// we use the order of the columns here to have similar download as the user view
columns.forEach((k) => {
set(
finalObject,
get(k, 'title', '') as string,
get(d, get(k, 'dataIndex', ''), 'n/a'),
);
if (label) {
set(finalObject, label as string, d[k]);
}
});
return finalObject as RowData;
}),
@@ -62,10 +61,15 @@ function GridTableComponent({
mutateDataSource = mutateDataSource.map(
(val): RowData => {
const newValue = val;
const newValue = { ...val };
Object.keys(val).forEach((k) => {
if (columnUnits[k]) {
newValue[k] = getYAxisFormattedValue(String(val[k]), columnUnits[k]);
// the check below takes care of not adding units for rows that have n/a values
newValue[k] =
val[k] !== 'n/a'
? getYAxisFormattedValue(String(val[k]), columnUnits[k])
: val[k];
newValue[`${k}_without_unit`] = val[k];
}
});
return newValue;
@@ -81,7 +85,6 @@ function GridTableComponent({
applyColumnUnits,
originalDataSource,
]);
useEffect(() => {
if (tableProcessedDataRef) {
// eslint-disable-next-line no-param-reassign

View File

@@ -1,4 +1,11 @@
import { ColumnsType, ColumnType } from 'antd/es/table';
import { ThresholdProps } from 'container/NewWidget/RightContainer/Threshold/types';
import { QUERY_TABLE_CONFIG } from 'container/QueryTable/config';
import { QueryTableProps } from 'container/QueryTable/QueryTable.intefaces';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { isEmpty, isNaN } from 'lodash-es';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
// Helper function to evaluate the condition based on the operator
function evaluateCondition(
@@ -56,3 +63,85 @@ export function findMatchingThreshold(
hasMultipleMatches,
};
}
export interface TableData {
columns: { name: string; queryName: string; isValueColumn: boolean }[];
rows: { data: any }[];
}
export function getQueryLegend(
currentQuery: Query,
queryName: string,
): string | undefined {
let legend: string | undefined;
switch (currentQuery.queryType) {
case EQueryType.QUERY_BUILDER:
// check if the value is present in the queries
legend = currentQuery.builder.queryData.find(
(query) => query.queryName === queryName,
)?.legend;
if (!legend) {
// check if the value is present in the formula
legend = currentQuery.builder.queryFormulas.find(
(query) => query.queryName === queryName,
)?.legend;
}
break;
case EQueryType.CLICKHOUSE:
legend = currentQuery.clickhouse_sql.find(
(query) => query.name === queryName,
)?.legend;
break;
case EQueryType.PROM:
legend = currentQuery.promql.find((query) => query.name === queryName)
?.legend;
break;
default:
legend = undefined;
break;
}
return legend;
}
export function createColumnsAndDataSource(
data: TableData,
currentQuery: Query,
renderColumnCell?: QueryTableProps['renderColumnCell'],
): { columns: ColumnsType<RowData>; dataSource: RowData[] } {
const columns: ColumnsType<RowData> =
data.columns?.reduce<ColumnsType<RowData>>((acc, item) => {
// is the column is the value column then we need to check for the available legend
const legend = item.isValueColumn
? getQueryLegend(currentQuery, item.queryName)
: undefined;
const column: ColumnType<RowData> = {
dataIndex: item.name,
// if no legend present then rely on the column name value
title: !isEmpty(legend) ? legend : item.name,
width: QUERY_TABLE_CONFIG.width,
render: renderColumnCell && renderColumnCell[item.name],
sorter: (a: RowData, b: RowData): number => {
const valueA = Number(a[`${item.name}_without_unit`] ?? a[item.name]);
const valueB = Number(b[`${item.name}_without_unit`] ?? b[item.name]);
if (!isNaN(valueA) && !isNaN(valueB)) {
return valueA - valueB;
}
return ((a[item.name] as string) || '').localeCompare(
(b[item.name] as string) || '',
);
},
};
return [...acc, column];
}, []) || [];
// the rows returned have data encapsulation hence removing the same here
const dataSource = data.rows?.map((d) => d.data) || [];
return { columns, dataSource };
}

View File

@@ -27,6 +27,7 @@ import {
import { useSelector } from 'react-redux';
import { NavLink } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { License } from 'types/api/licenses/def';
import AppReducer from 'types/reducer/app';
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
@@ -109,9 +110,13 @@ function HeaderContainer(): JSX.Element {
const { data: licenseData, isFetching, status: licenseStatus } = useLicense();
const licensesStatus: string =
licenseData?.payload?.licenses?.find((e: License) => e.isCurrent)?.status ||
'';
const isLicenseActive =
licenseData?.payload?.licenses?.find((e) => e.isCurrent)?.status ===
LICENSE_PLAN_STATUS.VALID;
licensesStatus?.toLocaleLowerCase() ===
LICENSE_PLAN_STATUS.VALID.toLocaleLowerCase();
useEffect(() => {
if (

View File

@@ -7,17 +7,20 @@ interface AlertInfoCardProps {
header: string;
subheader: string;
link: string;
onClick: () => void;
}
function AlertInfoCard({
header,
subheader,
link,
onClick,
}: AlertInfoCardProps): JSX.Element {
return (
<div
className="alert-info-card"
onClick={(): void => {
onClick();
window.open(link, '_blank');
}}
>

View File

@@ -2,6 +2,7 @@ import './AlertsEmptyState.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Divider, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
@@ -10,12 +11,26 @@ import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { DataSource } from 'types/common/queryBuilder';
import AppReducer from 'types/reducer/app';
import AlertInfoCard from './AlertInfoCard';
import { ALERT_CARDS, ALERT_INFO_LINKS } from './alertLinks';
import InfoLinkText from './InfoLinkText';
const alertLogEvents = (
title: string,
link: string,
dataSource?: DataSource,
): void => {
const attributes = {
link,
page: 'Alert empty state page',
};
logEvent(title, dataSource ? { ...attributes, dataSource } : attributes);
};
export function AlertsEmptyState(): JSX.Element {
const { t } = useTranslation('common');
const { role, featureResponse } = useSelector<AppState, AppReducer>(
@@ -91,18 +106,33 @@ export function AlertsEmptyState(): JSX.Element {
link="https://youtu.be/xjxNIqiv4_M"
leftIconVisible
rightIconVisible
onClick={(): void =>
alertLogEvents(
'Alert: Video tutorial link clicked',
'https://youtu.be/xjxNIqiv4_M',
)
}
/>
</div>
{ALERT_INFO_LINKS.map((info) => (
<InfoLinkText
key={info.link}
infoText={info.infoText}
link={info.link}
leftIconVisible={info.leftIconVisible}
rightIconVisible={info.rightIconVisible}
/>
))}
{ALERT_INFO_LINKS.map((info) => {
const logEventTriggered = (): void =>
alertLogEvents(
'Alert: Tutorial doc link clicked',
info.link,
info.dataSource,
);
return (
<InfoLinkText
key={info.link}
infoText={info.infoText}
link={info.link}
leftIconVisible={info.leftIconVisible}
rightIconVisible={info.rightIconVisible}
onClick={logEventTriggered}
/>
);
})}
</div>
</section>
<div className="get-started-text">
@@ -113,14 +143,23 @@ export function AlertsEmptyState(): JSX.Element {
</Divider>
</div>
{ALERT_CARDS.map((card) => (
<AlertInfoCard
key={card.link}
header={card.header}
subheader={card.subheader}
link={card.link}
/>
))}
{ALERT_CARDS.map((card) => {
const logEventTriggered = (): void =>
alertLogEvents(
'Alert: Sample alert link clicked',
card.link,
card.dataSource,
);
return (
<AlertInfoCard
key={card.link}
header={card.header}
subheader={card.subheader}
link={card.link}
onClick={logEventTriggered}
/>
);
})}
</div>
</div>
);

View File

@@ -6,6 +6,7 @@ interface InfoLinkTextProps {
link: string;
leftIconVisible: boolean;
rightIconVisible: boolean;
onClick: () => void;
}
function InfoLinkText({
@@ -13,10 +14,12 @@ function InfoLinkText({
link,
leftIconVisible,
rightIconVisible,
onClick,
}: InfoLinkTextProps): JSX.Element {
return (
<Flex
onClick={(): void => {
onClick();
window.open(link, '_blank');
}}
className="info-link-container"

View File

@@ -1,3 +1,5 @@
import { DataSource } from 'types/common/queryBuilder';
export const ALERT_INFO_LINKS = [
{
infoText: 'How to create Metrics-based alerts',
@@ -5,6 +7,7 @@ export const ALERT_INFO_LINKS = [
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
leftIconVisible: false,
rightIconVisible: true,
dataSource: DataSource.METRICS,
},
{
infoText: 'How to create Log-based alerts',
@@ -12,6 +15,7 @@ export const ALERT_INFO_LINKS = [
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
leftIconVisible: false,
rightIconVisible: true,
dataSource: DataSource.LOGS,
},
{
infoText: 'How to create Trace-based alerts',
@@ -19,6 +23,7 @@ export const ALERT_INFO_LINKS = [
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page',
leftIconVisible: false,
rightIconVisible: true,
dataSource: DataSource.TRACES,
},
];
@@ -26,24 +31,28 @@ export const ALERT_CARDS = [
{
header: 'Alert on high memory usage',
subheader: "Monitor your host's memory usage",
dataSource: DataSource.METRICS,
link:
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-memory-usage-for-host-goes-above-400-mb-or-any-fixed-memory',
},
{
header: 'Alert on slow external API calls',
subheader: 'Monitor your external API calls',
dataSource: DataSource.TRACES,
link:
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-external-api-latency-p90-is-over-1-second-for-last-5-mins',
},
{
header: 'Alert on high percentage of timeout errors in logs',
subheader: 'Monitor your logs for errors',
dataSource: DataSource.LOGS,
link:
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-empty-page#1-alert-when-percentage-of-redis-timeout-error-logs-greater-than-7-in-last-5-mins',
},
{
header: 'Alert on high error percentage of an endpoint',
subheader: 'Monitor your API endpoint',
dataSource: DataSource.METRICS,
link:
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-empty-page#3-alert-when-the-error-percentage-for-an-endpoint-exceeds-5',
},

View File

@@ -3,6 +3,7 @@ import { PlusOutlined } from '@ant-design/icons';
import { Input, Typography } from 'antd';
import type { ColumnsType } from 'antd/es/table/interface';
import saveAlertApi from 'api/alerts/save';
import logEvent from 'api/common/logEvent';
import DropDown from 'components/DropDown/DropDown';
import { listAlertMessage } from 'components/facingIssueBtn/util';
import {
@@ -41,7 +42,7 @@ import {
} from './styles';
import Status from './TableComponents/Status';
import ToggleAlertState from './ToggleAlertState';
import { filterAlerts } from './utils';
import { alertActionLogEvent, filterAlerts } from './utils';
const { Search } = Input;
@@ -55,6 +56,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
role,
);
const [editLoader, setEditLoader] = useState<boolean>(false);
const [cloneLoader, setCloneLoader] = useState<boolean>(false);
const params = useUrlQuery();
const orderColumnParam = params.get('columnKey');
const orderQueryParam = params.get('order');
@@ -104,15 +108,20 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
}, [notificationsApi, t]);
const onClickNewAlertHandler = useCallback(() => {
logEvent('Alert: New alert button clicked', {
number: allAlertRules?.length,
});
featureResponse
.refetch()
.then(() => {
history.push(ROUTES.ALERTS_NEW);
})
.catch(handleError);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [featureResponse, handleError]);
const onEditHandler = (record: GettableAlert) => (): void => {
setEditLoader(true);
featureResponse
.refetch()
.then(() => {
@@ -129,9 +138,11 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
params.set(QueryParams.ruleId, record.id.toString());
setEditLoader(false);
history.push(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
})
.catch(handleError);
.catch(handleError)
.finally(() => setEditLoader(false));
};
const onCloneHandler = (
@@ -143,33 +154,41 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
};
const apiReq = { data: copyAlert };
const response = await saveAlertApi(apiReq);
try {
setCloneLoader(true);
const response = await saveAlertApi(apiReq);
if (response.statusCode === 200) {
notificationsApi.success({
message: 'Success',
description: 'Alert cloned successfully',
});
if (response.statusCode === 200) {
notificationsApi.success({
message: 'Success',
description: 'Alert cloned successfully',
});
const { data: refetchData, status } = await refetch();
if (status === 'success' && refetchData.payload) {
setData(refetchData.payload || []);
setTimeout(() => {
const clonedAlert = refetchData.payload[refetchData.payload.length - 1];
params.set(QueryParams.ruleId, String(clonedAlert.id));
history.push(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
}, 2000);
}
if (status === 'error') {
const { data: refetchData, status } = await refetch();
if (status === 'success' && refetchData.payload) {
setData(refetchData.payload || []);
setTimeout(() => {
const clonedAlert = refetchData.payload[refetchData.payload.length - 1];
params.set(QueryParams.ruleId, String(clonedAlert.id));
history.push(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
}, 2000);
}
if (status === 'error') {
notificationsApi.error({
message: t('something_went_wrong'),
});
}
} else {
notificationsApi.error({
message: t('something_went_wrong'),
message: 'Error',
description: response.error || t('something_went_wrong'),
});
}
} else {
notificationsApi.error({
message: 'Error',
description: response.error || t('something_went_wrong'),
});
} catch (error) {
handleError();
console.error(error);
} finally {
setCloneLoader(false);
}
};
@@ -307,6 +326,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
width: 10,
render: (id: GettableAlert['id'], record): JSX.Element => (
<DropDown
onDropDownItemClick={(item): void => alertActionLogEvent(item.key, record)}
element={[
<ToggleAlertState
key="1"
@@ -314,10 +334,20 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
setData={setData}
id={id}
/>,
<ColumnButton key="2" onClick={onEditHandler(record)} type="link">
<ColumnButton
key="2"
onClick={onEditHandler(record)}
type="link"
loading={editLoader}
>
Edit
</ColumnButton>,
<ColumnButton key="3" onClick={onCloneHandler(record)} type="link">
<ColumnButton
key="3"
onClick={onCloneHandler(record)}
type="link"
loading={cloneLoader}
>
Clone
</ColumnButton>,
<DeleteAlert
@@ -332,6 +362,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
});
}
const paginationConfig = {
defaultCurrent: Number(paginationParam) || 1,
};
return (
<>
<SearchContainer>
@@ -361,11 +394,10 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
columns={columns}
rowKey="id"
dataSource={data}
shouldSendAlertsLogEvent
dynamicColumns={dynamicColumns}
onChange={handleChange}
pagination={{
defaultCurrent: Number(paginationParam) || 1,
}}
pagination={paginationConfig}
facingIssueBtn={{
attributes: {
screen: 'Alert list page',

View File

@@ -1,9 +1,11 @@
import { Space } from 'antd';
import getAll from 'api/alerts/getAll';
import logEvent from 'api/common/logEvent';
import ReleaseNote from 'components/ReleaseNote';
import Spinner from 'components/Spinner';
import { useNotifications } from 'hooks/useNotifications';
import { useEffect } from 'react';
import { isUndefined } from 'lodash-es';
import { useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useLocation } from 'react-router-dom';
@@ -19,8 +21,19 @@ function ListAlertRules(): JSX.Element {
cacheTime: 0,
});
const logEventCalledRef = useRef(false);
const { notifications } = useNotifications();
useEffect(() => {
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
logEvent('Alert: List page visited', {
number: data?.payload?.length,
});
logEventCalledRef.current = true;
}
}, [data?.payload]);
useEffect(() => {
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
notifications.error({

View File

@@ -27,5 +27,8 @@ export const ColumnButton = styled(ButtonComponent)`
padding-left: 0;
padding-right: 0;
margin-right: 1.5em;
width: 100%;
display: flex;
align-items: center;
}
`;

View File

@@ -1,3 +1,6 @@
import logEvent from 'api/common/logEvent';
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { GettableAlert } from 'types/api/alerts/get';
export const filterAlerts = (
@@ -23,3 +26,32 @@ export const filterAlerts = (
);
});
};
export const alertActionLogEvent = (
action: string,
record: GettableAlert,
): void => {
let actionValue = '';
switch (action) {
case '0':
actionValue = 'Enable/Disable';
break;
case '1':
actionValue = 'Edit';
break;
case '2':
actionValue = 'Clone';
break;
case '3':
actionValue = 'Delete';
break;
default:
break;
}
logEvent('Alert: Action', {
ruleId: record.id,
dataSource: ALERTS_DATA_SOURCE_MAP[record.alertType as AlertTypes],
name: record.alert,
action: actionValue,
});
};

View File

@@ -609,6 +609,16 @@ function DashboardsList(): JSX.Element {
</>
);
const paginationConfig = data.length > 20 && {
pageSize: 20,
showTotal: showPaginationItem,
showSizeChanger: false,
onChange: (page: any): void => handlePageSizeUpdate(page),
current: Number(sortOrder.pagination),
defaultCurrent: Number(sortOrder.pagination) || 1,
hideOnSinglePage: true,
};
return (
<div className="dashboards-list-container">
<div className="dashboards-list-view-content">
@@ -699,7 +709,16 @@ function DashboardsList(): JSX.Element {
New Dashboard
</Button>
</Dropdown>
<Button type="text" className="learn-more">
<Button
type="text"
className="learn-more"
onClick={(): void => {
window.open(
'https://signoz.io/docs/userguide/manage-dashboards?utm_source=product&utm_medium=dashboard-list-empty-state',
'_blank',
);
}}
>
Learn more
</Button>
<ArrowUpRight size={16} className="learn-more-arrow" />
@@ -813,16 +832,7 @@ function DashboardsList(): JSX.Element {
showSorterTooltip
loading={isDashboardListLoading || isFilteringDashboards}
showHeader={false}
pagination={
data.length > 20 && {
pageSize: 20,
showTotal: showPaginationItem,
showSizeChanger: false,
onChange: (page): void => handlePageSizeUpdate(page),
current: Number(sortOrder.pagination),
defaultCurrent: Number(sortOrder.pagination) || 1,
}
}
pagination={paginationConfig}
/>
</>
)}

View File

@@ -141,11 +141,6 @@ function ImportJSON({
colors: {
'editor.background': Color.BG_INK_300,
},
fontFamily: 'Space Mono',
fontSize: 20,
fontWeight: 'normal',
lineHeight: 18,
letterSpacing: -0.06,
});
}
@@ -233,6 +228,11 @@ function ImportJSON({
fontFamily: 'Space Mono',
}}
theme={isDarkMode ? 'my-theme' : 'light'}
onMount={(_, monaco): void => {
document.fonts.ready.then(() => {
monaco.editor.remeasureFonts();
});
}}
// eslint-disable-next-line react/jsx-no-bind
beforeMount={setEditorTheme}
/>

View File

@@ -8,7 +8,7 @@
.label {
color: var(--text-robin-400);
font-family: SF Mono;
font-family: 'Space Mono', monospace;
font-family: 'Geist Mono';
font-size: 13px;
font-weight: var(--font-weight-normal);
line-height: 18px;

View File

@@ -28,7 +28,7 @@ function JSONView({ logData }: JSONViewProps): JSX.Element {
},
fontWeight: 400,
// fontFamily: 'SF Mono',
fontFamily: 'Space Mono',
fontFamily: 'Geist Mono',
fontSize: 13,
lineHeight: '18px',
colorDecorators: true,

View File

@@ -53,8 +53,7 @@ function Overview({
enabled: false,
},
fontWeight: 400,
// fontFamily: 'SF Mono',
fontFamily: 'Space Mono',
fontFamily: 'Geist Mono',
fontSize: 13,
lineHeight: '18px',
colorDecorators: true,
@@ -80,12 +79,6 @@ function Overview({
colors: {
'editor.background': Color.BG_INK_400,
},
// fontFamily: 'SF Mono',
fontFamily: 'Space Mono',
fontSize: 12,
fontWeight: 'normal',
lineHeight: 18,
letterSpacing: -0.06,
});
}
@@ -124,6 +117,11 @@ function Overview({
onChange={(): void => {}}
height="20vh"
theme={isDarkMode ? 'my-theme' : 'light'}
onMount={(_, monaco): void => {
document.fonts.ready.then(() => {
monaco.editor.remeasureFonts();
});
}}
// eslint-disable-next-line react/jsx-no-bind
beforeMount={setEditorTheme}
/>

View File

@@ -57,6 +57,8 @@
background: rgba(22, 25, 34, 0.4);
.value-field {
font-family: 'Geist Mono';
position: relative;
}

View File

@@ -289,7 +289,13 @@ function TableView({
return (
<div className="value-field">
<CopyClipboardHOC textToCopy={textToCopy}>
<span style={{ color: Color.BG_SIENNA_400 }}>
<span
style={{
color: Color.BG_SIENNA_400,
whiteSpace: 'pre-wrap',
tabSize: 4,
}}
>
{removeEscapeCharacters(fieldData.value)}
</span>
</CopyClipboardHOC>

View File

@@ -9,6 +9,6 @@ export const CardStyled = styled(Card)`
height: 200px;
min-height: 200px;
padding: 0 16px 16px 16px;
font-family: 'Space Mono', monospace;
font-family: 'Geist Mono';
}
`;

View File

@@ -37,7 +37,7 @@ import { useNotifications } from 'hooks/useNotifications';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { FlatLogData } from 'lib/logs/flatLogData';
import { getPaginationQueryData } from 'lib/newQueryBuilder/getPaginationQueryData';
import { defaultTo, isEmpty, omit } from 'lodash-es';
import { cloneDeep, defaultTo, isEmpty, omit, set } from 'lodash-es';
import { Sliders } from 'lucide-react';
import { SELECTED_VIEWS } from 'pages/LogsExplorer/utils';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
@@ -63,10 +63,10 @@ import { v4 } from 'uuid';
function LogsExplorerViews({
selectedView,
showHistogram,
showFrequencyChart,
}: {
selectedView: SELECTED_VIEWS;
showHistogram: boolean;
showFrequencyChart: boolean;
}): JSX.Element {
const { notifications } = useNotifications();
const history = useHistory();
@@ -117,6 +117,12 @@ function LogsExplorerViews({
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
}, [stagedQuery]);
const { options, config } = useOptionsMenu({
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
dataSource: initialDataSource || DataSource.LOGS,
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
});
const orderByTimestamp: OrderByPayload | null = useMemo(() => {
const timestampOrderBy = listQuery?.orderBy.find(
(item) => item.columnName === 'timestamp',
@@ -174,10 +180,10 @@ function LogsExplorerViews({
() =>
updateAllQueriesOperators(
currentQuery || initialQueriesMap.logs,
PANEL_TYPES.TIME_SERIES,
selectedPanelType,
DataSource.LOGS,
),
[currentQuery, updateAllQueriesOperators],
[currentQuery, selectedPanelType, updateAllQueriesOperators],
);
const handleModeChange = (panelType: PANEL_TYPES): void => {
@@ -309,6 +315,14 @@ function LogsExplorerViews({
isLoading: isUpdateDashboardLoading,
} = useUpdateDashboard();
const getUpdatedQueryForExport = useCallback((): Query => {
const updatedQuery = cloneDeep(currentQuery);
set(updatedQuery, 'builder.queryData[0].pageSize', 10);
return updatedQuery;
}, [currentQuery]);
const handleExport = useCallback(
(dashboard: Dashboard | null): void => {
if (!dashboard || !panelType) return;
@@ -319,11 +333,17 @@ function LogsExplorerViews({
const widgetId = v4();
const query =
panelType === PANEL_TYPES.LIST
? getUpdatedQueryForExport()
: exportDefaultQuery;
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
dashboard,
exportDefaultQuery,
query,
widgetId,
panelTypeParam,
options.selectColumns,
);
updateDashboard(updatedDashboard, {
@@ -353,7 +373,7 @@ function LogsExplorerViews({
}
const dashboardEditView = generateExportToDashboardLink({
query: exportDefaultQuery,
query,
panelType: panelTypeParam,
dashboardId: data.payload?.uuid || '',
widgetId,
@@ -365,7 +385,9 @@ function LogsExplorerViews({
});
},
[
getUpdatedQueryForExport,
exportDefaultQuery,
options.selectColumns,
history,
notifications,
panelType,
@@ -460,12 +482,6 @@ function LogsExplorerViews({
selectedView,
]);
const { options, config } = useOptionsMenu({
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
dataSource: initialDataSource || DataSource.METRICS,
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
});
const chartData = useMemo(() => {
if (!stagedQuery) return [];
@@ -545,7 +561,7 @@ function LogsExplorerViews({
return (
<div className="logs-explorer-views-container">
{showHistogram && (
{showFrequencyChart && (
<LogsExplorerChart
className="logs-histogram"
isLoading={isFetchingListChartData || isLoadingListChartData}

View File

@@ -76,7 +76,10 @@ const renderer = (): RenderResult =>
<VirtuosoMockContext.Provider
value={{ viewportHeight: 300, itemHeight: 100 }}
>
<LogsExplorerViews selectedView={SELECTED_VIEWS.SEARCH} showHistogram />
<LogsExplorerViews
selectedView={SELECTED_VIEWS.SEARCH}
showFrequencyChart
/>
</VirtuosoMockContext.Provider>
</QueryBuilderProvider>
</MockQueryClientProvider>
@@ -120,11 +123,7 @@ describe('LogsExplorerViews -', () => {
// switch to table view
await userEvent.click(queryByTestId('table-view') as HTMLElement);
expect(
queryByText(
'Just a bit of patience, just a little bits enough ⎯ were getting your logs!',
),
).toBeInTheDocument();
expect(queryByText('pending_data_placeholder')).toBeInTheDocument();
});
it('check error state', async () => {

View File

@@ -1,8 +1,11 @@
import './LogsLoading.styles.scss';
import { Typography } from 'antd';
import { useTranslation } from 'react-i18next';
import { DataSource } from 'types/common/queryBuilder';
export function LogsLoading(): JSX.Element {
const { t } = useTranslation('common');
return (
<div className="loading-logs">
<div className="loading-logs-content">
@@ -13,8 +16,7 @@ export function LogsLoading(): JSX.Element {
/>
<Typography>
Just a bit of patience, just a little bits enough were getting your
logs!
{t('pending_data_placeholder', { dataSource: DataSource.LOGS })}
</Typography>
</div>
</div>

View File

@@ -5,7 +5,7 @@
height: 100%;
.resize-table {
height: calc(100% - 40px);
height: calc(100% - 70px);
overflow: scroll;
overflow-x: hidden;

View File

@@ -15,6 +15,7 @@ import {
} from 'hooks/useResourceAttribute/utils';
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { EQueryType } from 'types/common/dashboard';
import { v4 as uuid } from 'uuid';
@@ -93,6 +94,26 @@ function External(): JSX.Element {
[servicename, tagFilterItems],
);
const errorApmToTraceQuery = useGetAPMToTracesQueries({
servicename,
isExternalCall: true,
filters: [
{
id: uuid().slice(0, 8),
key: {
key: 'hasError',
dataType: DataTypes.bool,
type: 'tag',
isColumn: true,
isJSON: false,
id: 'hasError--bool--tag--true',
},
op: 'in',
value: ['true'],
},
],
});
const externalCallRPSWidget = useMemo(
() =>
getWidgetQueryBuilder({
@@ -156,7 +177,7 @@ function External(): JSX.Element {
servicename,
selectedTraceTags,
timestamp: selectedTimeStamp,
apmToTraceQuery,
apmToTraceQuery: errorApmToTraceQuery,
})}
>
View Traces

View File

@@ -58,6 +58,7 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-right: 16px;
.dashboard-breadcrumbs {
height: 48px;

View File

@@ -40,12 +40,46 @@
}
.variable-select {
.ant-select-dropdown {
max-width: 300px;
.ant-select-item {
display: flex;
align-items: center;
}
.all-label {
display: flex;
gap: 16px;
}
.dropdown-checkbox-label {
display: grid;
grid-template-columns: 24px 1fr;
}
.dropdown-value {
display: flex;
justify-content: space-between;
align-items: center;
.option-text {
max-width: 180px;
padding: 0 8px;
}
.toggle-tag-label {
padding-left: 8px;
right: 40px;
font-weight: normal;
position: absolute;
}
}
}
}
.dropdown-styles {
min-width: 300px;
max-width: 350px;
}
.lightMode {
.variable-item {
.variable-name {

View File

@@ -138,6 +138,7 @@ function DashboardVariableSelection(): JSX.Element | null {
}}
onValueUpdate={onValueUpdate}
variablesToGetUpdated={variablesToGetUpdated}
setVariablesToGetUpdated={setVariablesToGetUpdated}
/>
))}
</Row>

View File

@@ -54,6 +54,7 @@ describe('VariableItem', () => {
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
/>
</MockQueryClientProvider>,
);
@@ -69,6 +70,7 @@ describe('VariableItem', () => {
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
/>
</MockQueryClientProvider>,
);
@@ -83,6 +85,7 @@ describe('VariableItem', () => {
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
/>
</MockQueryClientProvider>,
);
@@ -111,6 +114,7 @@ describe('VariableItem', () => {
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
/>
</MockQueryClientProvider>,
);
@@ -123,6 +127,8 @@ describe('VariableItem', () => {
const customVariableData = {
...mockCustomVariableData,
allSelected: true,
showALLOption: true,
multiSelect: true,
};
render(
@@ -132,6 +138,7 @@ describe('VariableItem', () => {
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
/>
</MockQueryClientProvider>,
);
@@ -147,6 +154,7 @@ describe('VariableItem', () => {
existingVariables={{}}
onValueUpdate={mockOnValueUpdate}
variablesToGetUpdated={[]}
setVariablesToGetUpdated={(): void => {}}
/>
</MockQueryClientProvider>,
);

View File

@@ -1,15 +1,29 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable no-nested-ternary */
import './DashboardVariableSelection.styles.scss';
import { orange } from '@ant-design/colors';
import { WarningOutlined } from '@ant-design/icons';
import { Input, Popover, Select, Typography } from 'antd';
import {
Checkbox,
Input,
Popover,
Select,
Tag,
Tooltip,
Typography,
} from 'antd';
import { CheckboxChangeEvent } from 'antd/es/checkbox';
import dashboardVariablesQuery from 'api/dashboard/variables/dashboardVariablesQuery';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { commaValuesParser } from 'lib/dashbaordVariables/customCommaValuesParser';
import sortValues from 'lib/dashbaordVariables/sortVariableValues';
import { debounce, isArray, isString } from 'lodash-es';
import map from 'lodash-es/map';
import { memo, useEffect, useMemo, useState } from 'react';
import { ChangeEvent, memo, useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import { IDashboardVariable } from 'types/api/dashboard/getAll';
import { VariableResponseProps } from 'types/api/dashboard/variables/query';
@@ -23,6 +37,11 @@ const ALL_SELECT_VALUE = '__ALL__';
const variableRegexPattern = /\{\{\s*?\.([^\s}]+)\s*?\}\}/g;
enum ToggleTagValue {
Only = 'Only',
All = 'All',
}
interface VariableItemProps {
variableData: IDashboardVariable;
existingVariables: Record<string, IDashboardVariable>;
@@ -33,12 +52,17 @@ interface VariableItemProps {
allSelected: boolean,
) => void;
variablesToGetUpdated: string[];
setVariablesToGetUpdated: React.Dispatch<React.SetStateAction<string[]>>;
}
const getSelectValue = (
selectedValue: IDashboardVariable['selectedValue'],
variableData: IDashboardVariable,
): string | string[] => {
if (Array.isArray(selectedValue)) {
if (!variableData.multiSelect && selectedValue.length === 1) {
return selectedValue[0]?.toString() || '';
}
return selectedValue.map((item) => item.toString());
}
return selectedValue?.toString() || '';
@@ -50,6 +74,7 @@ function VariableItem({
existingVariables,
onValueUpdate,
variablesToGetUpdated,
setVariablesToGetUpdated,
}: VariableItemProps): JSX.Element {
const [optionsData, setOptionsData] = useState<(string | number | boolean)[]>(
[],
@@ -148,6 +173,10 @@ function VariableItem({
}
setOptionsData(newOptionsData);
} else {
setVariablesToGetUpdated((prev) =>
prev.filter((name) => name !== variableData.name),
);
}
}
} catch (e) {
@@ -193,7 +222,7 @@ function VariableItem({
});
const handleChange = (value: string | string[]): void => {
if (variableData.name)
if (variableData.name) {
if (
value === ALL_SELECT_VALUE ||
(Array.isArray(value) && value.includes(ALL_SELECT_VALUE)) ||
@@ -203,25 +232,29 @@ function VariableItem({
} else {
onValueUpdate(variableData.name, variableData.id, value, false);
}
}
};
// do not debounce the above function as we do not need debounce in select variables
const debouncedHandleChange = debounce(handleChange, 500);
const { selectedValue } = variableData;
const selectedValueStringified = useMemo(() => getSelectValue(selectedValue), [
selectedValue,
]);
const selectedValueStringified = useMemo(
() => getSelectValue(selectedValue, variableData),
[selectedValue, variableData],
);
const selectValue = variableData.allSelected
? 'ALL'
: selectedValueStringified;
const enableSelectAll = variableData.multiSelect && variableData.showALLOption;
const mode =
const selectValue =
variableData.allSelected && enableSelectAll
? 'ALL'
: selectedValueStringified;
const mode: 'multiple' | undefined =
variableData.multiSelect && !variableData.allSelected
? 'multiple'
: undefined;
const enableSelectAll = variableData.multiSelect && variableData.showALLOption;
useEffect(() => {
// Fetch options for CUSTOM Type
@@ -231,6 +264,117 @@ function VariableItem({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [variableData.type, variableData.customValue]);
const checkAll = (e: MouseEvent): void => {
e.stopPropagation();
e.preventDefault();
const isChecked =
variableData.allSelected || selectValue.includes(ALL_SELECT_VALUE);
if (isChecked) {
handleChange([]);
} else {
handleChange(ALL_SELECT_VALUE);
}
};
const handleOptionSelect = (
e: CheckboxChangeEvent,
option: string | number | boolean,
): void => {
const newSelectedValue = Array.isArray(selectedValue)
? ((selectedValue.filter(
(val) => val.toString() !== option.toString(),
) as unknown) as string[])
: [];
if (
!e.target.checked &&
Array.isArray(selectedValueStringified) &&
selectedValueStringified.includes(option.toString())
) {
if (newSelectedValue.length === 0) {
handleChange(ALL_SELECT_VALUE);
return;
}
if (newSelectedValue.length === 1) {
handleChange(newSelectedValue[0].toString());
return;
}
handleChange(newSelectedValue);
} else if (!e.target.checked && selectedValue === option.toString()) {
handleChange(ALL_SELECT_VALUE);
} else if (newSelectedValue.length === optionsData.length - 1) {
handleChange(ALL_SELECT_VALUE);
}
};
const [optionState, setOptionState] = useState({
tag: '',
visible: false,
});
function currentToggleTagValue({
option,
}: {
option: string;
}): ToggleTagValue {
if (
option.toString() === selectValue ||
(Array.isArray(selectValue) &&
selectValue?.includes(option.toString()) &&
selectValue.length === 1)
) {
return ToggleTagValue.All;
}
return ToggleTagValue.Only;
}
function handleToggle(e: ChangeEvent, option: string): void {
e.stopPropagation();
const mode = currentToggleTagValue({ option: option as string });
const isChecked =
variableData.allSelected ||
option.toString() === selectValue ||
(Array.isArray(selectValue) && selectValue?.includes(option.toString()));
if (isChecked) {
if (mode === ToggleTagValue.Only) {
handleChange(option.toString());
} else if (!variableData.multiSelect) {
handleChange(option.toString());
} else {
handleChange(ALL_SELECT_VALUE);
}
} else {
handleChange(option.toString());
}
}
function retProps(
option: string,
): {
onMouseOver: () => void;
onMouseOut: () => void;
} {
return {
onMouseOver: (): void =>
setOptionState({
tag: option.toString(),
visible: true,
}),
onMouseOut: (): void =>
setOptionState({
tag: option.toString(),
visible: false,
}),
};
}
const ensureValidOption = (option: string): boolean =>
!(
currentToggleTagValue({ option }) === ToggleTagValue.All && !enableSelectAll
);
return (
<div className="variable-item">
<Typography.Text className="variable-name" ellipsis>
@@ -264,19 +408,35 @@ function VariableItem({
onChange={handleChange}
bordered={false}
placeholder="Select value"
placement="bottomRight"
placement="bottomLeft"
mode={mode}
dropdownMatchSelectWidth={false}
style={SelectItemStyle}
loading={isLoading}
showSearch
data-testid="variable-select"
className="variable-select"
popupClassName="dropdown-styles"
maxTagCount={4}
getPopupContainer={popupContainer}
// eslint-disable-next-line react/no-unstable-nested-components
tagRender={(props): JSX.Element => (
<Tag closable onClose={props.onClose}>
{props.value}
</Tag>
)}
// eslint-disable-next-line react/no-unstable-nested-components
maxTagPlaceholder={(omittedValues): JSX.Element => (
<Tooltip title={omittedValues.map(({ value }) => value).join(', ')}>
<span>+ {omittedValues.length} </span>
</Tooltip>
)}
>
{enableSelectAll && (
<Select.Option data-testid="option-ALL" value={ALL_SELECT_VALUE}>
ALL
<div className="all-label" onClick={(e): void => checkAll(e as any)}>
<Checkbox checked={variableData.allSelected} />
ALL
</div>
</Select.Option>
)}
{map(optionsData, (option) => (
@@ -285,7 +445,45 @@ function VariableItem({
key={option.toString()}
value={option}
>
{option.toString()}
<div
className={variableData.multiSelect ? 'dropdown-checkbox-label' : ''}
>
{variableData.multiSelect && (
<Checkbox
onChange={(e): void => {
e.stopPropagation();
e.preventDefault();
handleOptionSelect(e, option);
}}
checked={
variableData.allSelected ||
option.toString() === selectValue ||
(Array.isArray(selectValue) &&
selectValue?.includes(option.toString()))
}
/>
)}
<div
className="dropdown-value"
{...retProps(option as string)}
onClick={(e): void => handleToggle(e as any, option as string)}
>
<Tooltip title={option.toString()} placement="bottomRight">
<Typography.Text ellipsis className="option-text">
{option.toString()}
</Typography.Text>
</Tooltip>
{variableData.multiSelect &&
optionState.tag === option.toString() &&
optionState.visible &&
ensureValidOption(option as string) && (
<Typography.Text className="toggle-tag-label">
{currentToggleTagValue({ option: option as string })}
</Typography.Text>
)}
</div>
</div>
</Select.Option>
))}
</Select>

View File

@@ -42,4 +42,5 @@ export const VariableValue = styled(Typography)`
export const SelectItemStyle = {
minWidth: 120,
fontSize: '0.8rem',
width: '100%',
};

View File

@@ -87,9 +87,6 @@ function ClickHouseQueryBuilder({
'editor.background': Color.BG_INK_300,
},
});
document.fonts.ready.then(() => {
monaco.editor.remeasureFonts();
});
}
return (
@@ -105,6 +102,11 @@ function ClickHouseQueryBuilder({
height="200px"
onChange={handleUpdateEditor}
value={queryData.query}
onMount={(_, monaco): void => {
document.fonts.ready.then(() => {
monaco.editor.remeasureFonts();
});
}}
options={{
scrollbar: {
alwaysConsumeMouseWheel: false,

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