Compare commits

..

80 Commits

Author SHA1 Message Date
Piyush Singariya
c9f5b16c37 chore: restructuring looks ok 2025-04-21 17:39:19 +05:30
Piyush Singariya
55837dacb5 chore: in progress 2025-04-18 17:37:50 +05:30
Piyush Singariya
14248968cb chore: trying restructuring 2025-04-18 16:26:32 +05:30
Piyush Singariya
a436ae900c feat: introducing S3 sync as AWS integration 2025-04-16 18:45:36 +05:30
Nityananda Gohain
5dd02a5b8e fix: remove unnecssary code for email domain check error (#7566)
* fix: proper check for emailComponents

* fix: correct error handling
2025-04-14 11:15:21 +05:30
Srikanth Chekuri
c0f01e4cb9 chore: add metadatastore implementation for logs and traces (#7559)
* chore: add metadatastore implementation for logs and traces

* chore: use telemetrystore mock
2025-04-11 19:41:02 +05:30
Srikanth Chekuri
fed84cb50a chore: add condition builder attributes metadata (#7558) 2025-04-11 16:20:27 +05:30
Srikanth Chekuri
80545c4d07 chore: add materialized field extractor from table schema (#7557) 2025-04-11 15:53:55 +05:30
Srikanth Chekuri
0b1faec092 chore: add condition builder for span index v3 (#7556) 2025-04-11 15:13:04 +05:30
Srikanth Chekuri
ba6f31b1c3 chore: add virtual fields table (#7586) 2025-04-11 07:36:31 +05:30
Srikanth Chekuri
eed92978a4 chore: add non-json condition builder for logs v2 (#7555) 2025-04-10 18:23:01 +00:00
Prashant Shahi
41cbd316b5 Feat/staging (#7585)
### Summary

- Non-production build workflow using Primus
- Staging CD: new staging app and dev staging deployments
- cleanup used docker resources in stagingapp/testingapp machines

---------

Signed-off-by: Prashant Shahi <prashant@signoz.io>
2025-04-10 17:46:13 +05:30
Vishal Sharma
8d7d33393d feat: include tenant_url in event attributes for logging (#7582) 2025-04-10 15:17:14 +05:30
sawhil
8d143b44b1 feat: removed ff for tp-api-monitoring from fe - 1 2025-04-09 15:44:42 +05:30
sawhil
423aebd6eb feat: removed ff for tp-api-monitoring from fe 2025-04-09 15:44:42 +05:30
primus-bot[bot]
8d630707af chore(release): bump to v0.78.1 (#7573)
#### Summary
 - Release SigNoz v0.78.1

 Created by [Primus-Bot](https://github.com/apps/primus-bot)

Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2025-04-09 15:26:04 +05:30
Prashant Shahi
a5b52431b7 chore(build): include missing LDFLAGS in the community/enterprise builds (#7571)
### Summary

- include missing LDFLAGS in the community/enterprise builds

---------

Signed-off-by: Prashant Shahi <prashant@signoz.io>
2025-04-09 15:10:49 +05:30
primus-bot[bot]
0138d757c8 chore(release): bump to v0.78.0 (#7568)
Co-authored-by: primus-bot[bot] <171087277+primus-bot[bot]@users.noreply.github.com>
2025-04-09 12:28:18 +05:30
SagarRajput-7
844195b84f Revert "fix: reevaluated newdashboard bool to ensure that widget doesn't exis…" (#7567)
This reverts commit e53d3d1269.
2025-04-09 10:38:31 +05:30
Srikanth Chekuri
8ff05b2e8f chore: add field type definitions for qb v5 (#7552) 2025-04-08 22:34:58 +05:30
Srikanth Chekuri
c8c56c544e chore: add generated parser files for go (#7538) 2025-04-08 13:52:40 +00:00
Vikrant Gupta
1c43655336 fix(ff): alert creation disabled for non metric alerts (#7561) 2025-04-08 13:21:59 +00:00
Prashant Shahi
c269c8c6b8 feat: publish signoz to multiple registry using primus (#7504)
### Summary

- publish signoz images to multiple registry using primus
- deprecate old build workflow

---------

Signed-off-by: Prashant Shahi <prashant@signoz.io>
2025-04-08 18:06:22 +05:30
Vibhu Pandey
3142b6cc6d chore(ff): remove some more unused ffs (#7532)
### Summary

remove unused feature flags
- ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
- BASIC_PLAN = 'BASIC_PLAN',
- ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK',
- ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK',
- ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY',
- ALERT_CHANNEL_OPSGENIE = 'ALERT_CHANNEL_OPSGENIE',
- ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS',
- CUSTOM_METRICS_FUNCTION = 'CUSTOM_METRICS_FUNCTION',
- QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS',
- QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
- DISABLE_UPSELL = 'DISABLE_UPSELL',
- OSS = 'OSS',
- QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
- AWS_INTEGRATION = 'AWS_INTEGRATION',

remove ProPlan concept
2025-04-07 22:58:46 +05:30
Vibhu Pandey
58e141685a revert(smart): add smart trace detail search back (#7547)
### Summary

- Added the trace search endpoint back
- Enabled the `smart` algorithm for all users
- Added `"algo":"smart"` for telemetry events in the old endpoint
2025-04-07 17:19:39 +00:00
Srikanth Chekuri
e17f63a50c chore: error log on query error (#7553)
* chore: error log or query error

* chore: no error on context cancel
2025-04-07 15:15:49 +00:00
Vibhu Pandey
838ef5dcc5 feat(valuer): add string valuer (#7554) 2025-04-07 20:36:04 +05:30
SagarRajput-7
e53d3d1269 fix: reevaluated newdashboard bool to ensure that widget doesn't exist, incase of a prior PUT call (#7525)
* fix: reevaluated newdashboard bool to ensure that widget doesn't exist, incase of a prior PUT call

* fix: resolved comment
2025-04-07 15:31:08 +05:30
Vibhu Pandey
2330420c0d fix(querier): remove ff (#7531) 2025-04-05 18:08:06 +00:00
Vibhu Pandey
65ac277074 fix(duration|timestamp): remove duration/timestamp sort (#7530) 2025-04-05 23:26:50 +05:30
Vibhu Pandey
b7982ca348 fix(ff): remove feature interface from ruler (#7529)
### Summary

remove feature interface from ruler
2025-04-05 12:52:26 +00:00
dependabot[bot]
2748b49a44 chore(deps): bump github.com/golang-jwt/jwt/v5 from 5.2.1 to 5.2.2 (#7401)
Bumps [github.com/golang-jwt/jwt/v5](https://github.com/golang-jwt/jwt) from 5.2.1 to 5.2.2.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v5.2.1...v5.2.2)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-04-05 01:43:43 +00:00
Vibhu Pandey
7345027762 fix(ff): remove prefer rpm (#7528) 2025-04-04 23:38:16 +05:30
Vibhu Pandey
68f874e433 chore(ff): remove unused SMART_TRACE_DETAIL feature flag (#7527) 2025-04-04 20:28:54 +05:30
Vibhu Pandey
54a82b1664 fix(dashboards): remove ff interface (#7526) 2025-04-04 19:12:31 +05:30
Yunus M
93dc585145 fix: disable sidenav items for cloud users whose license has expired (#7524) 2025-04-04 18:33:55 +05:30
Vikrant Gupta
6a143efd2c feat(sqlmigration): update the user related tables according to new schema (#7518)
* feat(sqlmigration): update the alertmanager tables

* feat(sqlmigration): update the alertmanager tables

* feat(sqlmigration): make the preference package multi tenant

* feat(preference): address nit pick comments

* feat(preference): added the cascade delete for preferences

* feat(sqlmigration): update apdex and TTL status tables  (#7481)

* feat(sqlmigration): update the apdex and ttl tables

* feat(sqlmigration): register the new migration and rename table

* feat(sqlmigration): fix the ttl queries

* feat(sqlmigration): update the TTL and apdex tables

* feat(sqlmigration): update the TTL and apdex tables

* feat(sqlmigration): fix the reset password and pat tables (#7482)

* feat(sqlmigration): fix the reset password and pat tables

* feat(sqlmigration): revert PAT changes

* feat(sqlmigration): register and rename the new migration

* feat(sqlmigration): handle updates for user tables

* feat(sqlmigration): remove unwanted changes
2025-04-04 01:46:28 +05:30
Vikrant Gupta
0116eb20ab feat(sqlmigration): update apdex and TTL status tables (#7517)
* feat(sqlmigration): update the alertmanager tables

* feat(sqlmigration): update the alertmanager tables

* feat(sqlmigration): make the preference package multi tenant

* feat(preference): address nit pick comments

* feat(preference): added the cascade delete for preferences

* feat(sqlmigration): update apdex and TTL status tables  (#7481)

* feat(sqlmigration): update the apdex and ttl tables

* feat(sqlmigration): register the new migration and rename table

* feat(sqlmigration): fix the ttl queries

* feat(sqlmigration): update the TTL and apdex tables

* feat(sqlmigration): update the TTL and apdex tables
2025-04-04 01:36:47 +05:30
Vikrant Gupta
79e9d1b357 feat(preference): multi tenant preference module (#7516)
* feat(sqlmigration): update the alertmanager tables

* feat(sqlmigration): update the alertmanager tables

* feat(sqlmigration): make the preference package multi tenant

* feat(preference): address nit pick comments

* feat(preference): added the cascade delete for preferences
2025-04-04 01:25:24 +05:30
Vikrant Gupta
b89ce82e25 feat(sqlmigration): update the alertmanager tables (#7513)
* feat(sqlmigration): update the alertmanager tables
2025-04-03 17:56:49 +00:00
dependabot[bot]
b43a198fd8 chore(deps): bump github.com/expr-lang/expr from 1.16.9 to 1.17.0 (#7342)
Bumps [github.com/expr-lang/expr](https://github.com/expr-lang/expr) from 1.16.9 to 1.17.0.
- [Release notes](https://github.com/expr-lang/expr/releases)
- [Commits](https://github.com/expr-lang/expr/compare/v1.16.9...v1.17.0)

---
updated-dependencies:
- dependency-name: github.com/expr-lang/expr
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-04-03 08:17:40 +00:00
Srikanth Chekuri
b40ca4baf3 fix: do not crash service on panic during rule eval (#7514) 2025-04-03 13:13:58 +05:30
SagarRajput-7
8df77c9221 fix: fixed trace funnel - header style overriding other pages (#7512)
* fix: fixed trace funnel - header style overriding other pages

* fix: fixed trace funnel - header style overriding other pages

* fix: handled nesting
2025-04-03 07:29:39 +00:00
Srikanth Chekuri
f67555576f chore: add info icon tool tips for webhook/routing/integration key (#7405) 2025-04-03 09:40:41 +05:30
Srikanth Chekuri
f0a4c37073 fix: handle maintenance windows that cross day boundaries (#7494) 2025-04-02 20:48:01 +00:00
Vikrant Gupta
7972261237 Revert "fix: use search v2 component for traces data source & minor improveme…" (#7511)
This reverts commit d7a6607a25.
2025-04-03 01:15:20 +05:30
primus-bot[bot]
3b4a8e5e0f chore(release): bump to v0.77.0 (#7502)
#### Summary
 - Release SigNoz v0.77.0
 - Bump SigNoz OTel Collector to v0.111.37

 Created by [Primus-Bot](https://github.com/apps/primus-bot)
2025-04-02 12:19:03 +05:30
Yunus M
5ef3b8ee3f feat: support request data source and improve layout (#7485)
* feat: support request data source and improve layout

* feat: update config

* feat: update config with related keywords

* update config

---------

Co-authored-by: makeavish <makeavish786@gmail.com>
2025-04-02 11:59:53 +05:30
Yunus M
597752a4bc fix: licenses in community edition & improve messaging (#7456)
Enhance platform to handle cloud, self-hosted, community, and enterprise user types with tailored routing, error handling, and feature access.
2025-04-02 01:12:42 +05:30
Nityananda Gohain
07a244f569 chore: add migration for pat to add default values (#7492)
* chore: add migration for pat to add default values

* fix: minor changes

* fix: don't panic in GetClickhouseColumnName

* fix: use new function for pat

* fix: address minor comments

* fix: address comments

* fix: remove generatepat

* fix: minor changes

* fix: remove extra check

---------

Co-authored-by: Vibhu Pandey <vibhupandey28@gmail.com>
2025-04-01 23:49:37 +05:30
sawhil
eb9385840f fix: minor error handler message 2025-04-01 22:47:48 +05:30
sawhil
30b689037a fix: minor fix 2025-04-01 22:47:48 +05:30
sawhil
ba33c885d5 fix: added auth token refresh logic in event source provider error handler 2025-04-01 22:47:48 +05:30
Amlan Kumar Nandy
a4ed9e4d47 feat: base setup for inspect metrics feature (#7490) 2025-04-01 11:47:38 +00:00
Nityananda Gohain
df5767198c fix: don't panic in GetClickhouseColumnName (#7493) 2025-03-31 22:26:37 +05:30
Vibhu Pandey
81c7f3221a feat(prometheus): create a dedicated prometheus package (#7397) 2025-03-31 14:11:11 +00:00
Amlan Kumar Nandy
2cbd8733a1 chore: remove new banner from infra monitoring tab in side nav (#7483) 2025-03-31 13:22:23 +00:00
Nityananda Gohain
71d1dfe9bd chore: use new uuid in pipelines (#7487) 2025-03-31 16:45:00 +05:30
aniketio-ctrl
459712d25c fix(nil-pointer): wrong error passed | 2262 (#7463)
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-03-31 09:47:37 +00:00
Aditya Singh
61de2d414d fix: handle 404 redirection on root route (#7454)
* fix: handle 404 redirection on root route

* fix: add home component for root route

---------

Co-authored-by: Aditya Singh <adityasingh@Adityas-MacBook-Pro.local>
2025-03-31 14:35:58 +05:30
Sahil Khan
0b7cd4c1a7 feat: api monitoring feedback - 2 (#7432)
* feat: new dropdown styles

* fix: added new tag

* feat: added endpoint name and port in endpoint details

* feat: endpoint details feedback

* feat: analytics added

* fix: title fixed

* fix: domain list breaking for non available data

* feat: added third party api feature flag

* fix: console removed

* feat: added traces corelation in api monitoring charts

* feat: added customondragselect in grid card full view to handle breaking flow

* fix: minor failsafes added:

* fix: minor ux fix

* feat: incorporated pr comments - 0
2025-03-30 03:10:43 +05:30
Shaheer Kochai
62c033ccf8 chore: trace funnels feature flag changes (#7478)
* chore: trace funnels feature flag
2025-03-29 19:33:15 +05:30
Nageshbansal
e637487984 Fix the hyperlink for otel-demo-docs in contributing guide (#7462)
Co-authored-by: CheetoDa <31571545+Calm-Rock@users.noreply.github.com>
Co-authored-by: Vibhu Pandey <vibhupandey28@gmail.com>
2025-03-28 14:58:11 +00:00
Nityananda Gohain
8fc43a00f8 fix: collector connection to opamp without orgID (#7474) 2025-03-28 20:19:37 +05:30
Srikanth Chekuri
031d62ca44 Revert "fix: added default value to time,space aggregation to fix query_range…" (#7464)
This reverts commit 8c4c357351.
2025-03-28 12:43:31 +05:30
SagarRajput-7
8c4c357351 fix: added default value to time,space aggregation to fix query_range getting 500 for metric (#7414)
* fix: added default value to time,space aggregation to fix query_range getting 500 for metric

* fix: added all available operators as default when no attribute type is present

* fix: changed operator, time and space values to avg when empty attribute type
2025-03-28 06:36:09 +00:00
SagarRajput-7
d8d8191a32 feat: allow width customisation and persist it across users and view (#7273)
* feat: removed ellipsis prop

* feat: prevent unnecessary save calls

* feat: fix dashboard detail resize icon

* feat: adjusted resizable header - set minConstraint

* feat: fixed dashboard vanishing issue

* feat: removed dependency causing maximum callstack warning

* feat: corrected the list edit view render issue and resize handler fix

* feat: style fix

* feat: removed comments

* fix: updated test cases

* feat: updated the test cases

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2025-03-28 06:06:40 +00:00
SagarRajput-7
a876c0a744 chore: added doc title to messaging queues (#7460) 2025-03-28 11:25:41 +05:30
Vishal Sharma
c36f913a90 fix: telemetry version function call (#7453) 2025-03-27 20:07:09 +05:30
SagarRajput-7
ed597f00c0 fix: fixed copy to clipboard popup getting flooded on every click (#7448) 2025-03-27 05:54:34 +00:00
Vibhu Pandey
4957d3ae93 feat(sqlstore): move postgres to enterprise codebase (#7445) 2025-03-27 11:16:43 +05:30
Srikanth Chekuri
8835e3493d chore: skip logfield/spanfield type in the suggestions (#7433) 2025-03-27 10:36:27 +05:30
Vibhu Pandey
027a1631ef feat(httpclient): add an extensible http client (#7446) 2025-03-26 19:33:52 +00:00
Shaheer Kochai
d7a6607a25 fix: use search v2 component for traces data source & minor improvements to search v2 component (#7404) 2025-03-26 18:00:54 +00:00
Sahil Khan
7a58bc58c9 fix: stage and run query button same url navigation enabled (#7415) 2025-03-26 23:25:01 +05:30
Srikanth Chekuri
88be23c3e3 chore: pass through substitutions for CH query (#7389) 2025-03-26 12:58:55 +00:00
Srikanth Chekuri
8f095dfbc9 fix: handle expected value less than zero (#7410) 2025-03-26 12:50:46 +00:00
aniketio-ctrl
72207691a3 fix(metrics-explorer): added time filter in inner sub queries of list and samples (#7436) 2025-03-26 09:57:21 +00:00
Raj Kamal Singh
8998ca652e chore: aws integration: bump recommended agent version (#7434) 2025-03-26 09:14:05 +00:00
Piyush Singariya
f4ae5f19ff feat: AWS Managed Streaming Kafka service integration (#7350)
* feat: msk integration

* feat: logs not available in msk

* fix: minor suggestions made by ellipsis

* fix: changes based on review, added Variables, Units, Legends, SVG

* fix: update in global variables, and query operators

* fix: update in rx tx panel, region variable query update

---------

Co-authored-by: Raj Kamal Singh <1133322+raj-k-singh@users.noreply.github.com>
2025-03-26 12:57:39 +05:30
338 changed files with 20835 additions and 4399 deletions

81
.github/workflows/build-community.yaml vendored Normal file
View File

@@ -0,0 +1,81 @@
name: build-community
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+'
defaults:
run:
shell: bash
env:
PRIMUS_HOME: .primus
MAKE: make --no-print-directory --makefile=.primus/src/make/main.mk
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.build-info.outputs.version }}
hash: ${{ steps.build-info.outputs.hash }}
time: ${{ steps.build-info.outputs.time }}
branch: ${{ steps.build-info.outputs.branch }}
steps:
- name: self-checkout
uses: actions/checkout@v4
- id: token
name: github-token-gen
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.PRIMUS_APP_ID }}
private-key: ${{ secrets.PRIMUS_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: primus-checkout
uses: actions/checkout@v4
with:
repository: signoz/primus
ref: main
path: .primus
token: ${{ steps.token.outputs.token }}
- name: build-info
run: |
echo "version=$($MAKE info-version)" >> $GITHUB_OUTPUT
echo "hash=$($MAKE info-commit-short)" >> $GITHUB_OUTPUT
echo "time=$($MAKE info-timestamp)" >> $GITHUB_OUTPUT
echo "branch=$($MAKE info-branch)" >> $GITHUB_OUTPUT
js-build:
uses: signoz/primus.workflows/.github/workflows/js-build.yaml@main
needs: prepare
secrets: inherit
with:
PRIMUS_REF: main
JS_SRC: frontend
JS_OUTPUT_ARTIFACT_CACHE_KEY: community-jsbuild-${{ github.sha }}
JS_OUTPUT_ARTIFACT_PATH: frontend/build
DOCKER_BUILD: false
DOCKER_MANIFEST: false
go-build:
uses: signoz/primus.workflows/.github/workflows/go-build.yaml@main
needs: [prepare, js-build]
secrets: inherit
with:
PRIMUS_REF: main
GO_NAME: signoz-community
GO_INPUT_ARTIFACT_CACHE_KEY: community-jsbuild-${{ github.sha }}
GO_INPUT_ARTIFACT_PATH: frontend/build
GO_BUILD_CONTEXT: ./pkg/query-service
GO_BUILD_FLAGS: >-
-tags timetzdata
-ldflags='-linkmode external -extldflags \"-static\" -s -w
-X github.com/SigNoz/signoz/pkg/version.version=${{ needs.prepare.outputs.version }}
-X github.com/SigNoz/signoz/pkg/version.variant=community
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
-X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }}
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}'
GO_CGO_ENABLED: 1
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
DOCKER_DOCKERFILE_PATH: ./pkg/query-service/Dockerfile.multi-arch
DOCKER_MANIFEST: true
DOCKER_PROVIDERS: dockerhub

113
.github/workflows/build-enterprise.yaml vendored Normal file
View File

@@ -0,0 +1,113 @@
name: build-enterprise
on:
push:
tags:
- v*
defaults:
run:
shell: bash
env:
PRIMUS_HOME: .primus
MAKE: make --no-print-directory --makefile=.primus/src/make/main.mk
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
docker_providers: ${{ steps.set-docker-providers.outputs.providers }}
version: ${{ steps.build-info.outputs.version }}
hash: ${{ steps.build-info.outputs.hash }}
time: ${{ steps.build-info.outputs.time }}
branch: ${{ steps.build-info.outputs.branch }}
steps:
- name: self-checkout
uses: actions/checkout@v4
- id: token
name: github-token-gen
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.PRIMUS_APP_ID }}
private-key: ${{ secrets.PRIMUS_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: primus-checkout
uses: actions/checkout@v4
with:
repository: signoz/primus
ref: main
path: .primus
token: ${{ steps.token.outputs.token }}
- name: build-info
id: build-info
run: |
echo "version=$($MAKE info-version)" >> $GITHUB_OUTPUT
echo "hash=$($MAKE info-commit-short)" >> $GITHUB_OUTPUT
echo "time=$($MAKE info-timestamp)" >> $GITHUB_OUTPUT
echo "branch=$($MAKE info-branch)" >> $GITHUB_OUTPUT
- name: set-docker-providers
id: set-docker-providers
run: |
if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ || ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then
echo "providers=dockerhub gcp" >> $GITHUB_OUTPUT
else
echo "providers=gcp" >> $GITHUB_OUTPUT
fi
- name: create-dotenv
run: |
mkdir -p frontend
echo 'CI=1' > frontend/.env
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' >> frontend/.env
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
echo 'SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
echo 'SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
echo 'SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
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
echo 'CUSTOMERIO_ID="${{ secrets.CUSTOMERIO_ID }}"' >> frontend/.env
echo 'CUSTOMERIO_SITE_ID="${{ secrets.CUSTOMERIO_SITE_ID }}"' >> frontend/.env
- name: cache-dotenv
uses: actions/cache@v4
with:
path: frontend/.env
key: dotenv-${{ github.sha }}
js-build:
uses: signoz/primus.workflows/.github/workflows/js-build.yaml@main
needs: prepare
secrets: inherit
with:
PRIMUS_REF: main
JS_SRC: frontend
JS_INPUT_ARTIFACT_CACHE_KEY: dotenv-${{ github.sha }}
JS_INPUT_ARTIFACT_PATH: frontend/.env
JS_OUTPUT_ARTIFACT_CACHE_KEY: jsbuild-${{ github.sha }}
JS_OUTPUT_ARTIFACT_PATH: frontend/build
DOCKER_BUILD: false
DOCKER_MANIFEST: false
go-build:
uses: signoz/primus.workflows/.github/workflows/go-build.yaml@main
needs: [prepare, js-build]
secrets: inherit
with:
PRIMUS_REF: main
GO_INPUT_ARTIFACT_CACHE_KEY: jsbuild-${{ github.sha }}
GO_INPUT_ARTIFACT_PATH: frontend/build
GO_BUILD_CONTEXT: ./ee/query-service
GO_BUILD_FLAGS: >-
-tags timetzdata
-ldflags='-linkmode external -extldflags \"-static\" -s -w
-X github.com/SigNoz/signoz/pkg/version.version=${{ needs.prepare.outputs.version }}
-X github.com/SigNoz/signoz/pkg/version.variant=enterprise
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
-X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }}
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.signoz.cloud
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.signoz.io/api/v1'
GO_CGO_ENABLED: 1
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch
DOCKER_MANIFEST: true
DOCKER_PROVIDERS: ${{ needs.prepare.outputs.docker_providers }}

131
.github/workflows/build-staging.yaml vendored Normal file
View File

@@ -0,0 +1,131 @@
name: build-staging
on:
push:
branches:
- main
pull_request:
types: [labeled]
defaults:
run:
shell: bash
env:
PRIMUS_HOME: .primus
MAKE: make --no-print-directory --makefile=.primus/src/make/main.mk
jobs:
prepare:
runs-on: ubuntu-latest
if: ${{ contains(github.event.label.name, 'staging:') || github.event.ref == 'refs/heads/main' }}
outputs:
version: ${{ steps.build-info.outputs.version }}
hash: ${{ steps.build-info.outputs.hash }}
time: ${{ steps.build-info.outputs.time }}
branch: ${{ steps.build-info.outputs.branch }}
deployment: ${{ steps.build-info.outputs.deployment }}
steps:
- name: self-checkout
uses: actions/checkout@v4
- id: token
name: github-token-gen
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.PRIMUS_APP_ID }}
private-key: ${{ secrets.PRIMUS_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: primus-checkout
uses: actions/checkout@v4
with:
repository: signoz/primus
ref: main
path: .primus
token: ${{ steps.token.outputs.token }}
- name: build-info
id: build-info
run: |
echo "version=$($MAKE info-version)" >> $GITHUB_OUTPUT
echo "hash=$($MAKE info-commit-short)" >> $GITHUB_OUTPUT
echo "time=$($MAKE info-timestamp)" >> $GITHUB_OUTPUT
echo "branch=$($MAKE info-branch)" >> $GITHUB_OUTPUT
staging_label="${{ github.event.label.name }}"
if [[ "${staging_label}" == "staging:"* ]]; then
deployment=${staging_label#"staging:"}
elif [[ "${{ github.event.ref }}" == "refs/heads/main" ]]; then
deployment="staging"
else
echo "error: not able to determine deployment - please verify the PR label or the branch"
exit 1
fi
echo "deployment=${deployment}" >> $GITHUB_OUTPUT
- name: create-dotenv
run: |
mkdir -p frontend
echo 'CI=1' > frontend/.env
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' >> frontend/.env
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
echo 'SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
echo 'SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
echo 'SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
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
echo 'CUSTOMERIO_ID="${{ secrets.CUSTOMERIO_ID }}"' >> frontend/.env
echo 'CUSTOMERIO_SITE_ID="${{ secrets.CUSTOMERIO_SITE_ID }}"' >> frontend/.env
- name: cache-dotenv
uses: actions/cache@v4
with:
path: frontend/.env
key: dotenv-${{ github.sha }}
js-build:
uses: signoz/primus.workflows/.github/workflows/js-build.yaml@main
needs: prepare
secrets: inherit
with:
PRIMUS_REF: main
JS_SRC: frontend
JS_INPUT_ARTIFACT_CACHE_KEY: dotenv-${{ github.sha }}
JS_INPUT_ARTIFACT_PATH: frontend/.env
JS_OUTPUT_ARTIFACT_CACHE_KEY: jsbuild-${{ github.sha }}
JS_OUTPUT_ARTIFACT_PATH: frontend/build
DOCKER_BUILD: false
DOCKER_MANIFEST: false
go-build:
uses: signoz/primus.workflows/.github/workflows/go-build.yaml@main
needs: [prepare, js-build]
secrets: inherit
with:
PRIMUS_REF: main
GO_INPUT_ARTIFACT_CACHE_KEY: jsbuild-${{ github.sha }}
GO_INPUT_ARTIFACT_PATH: frontend/build
GO_BUILD_CONTEXT: ./ee/query-service
GO_BUILD_FLAGS: >-
-tags timetzdata
-ldflags='-linkmode external -extldflags \"-static\" -s -w
-X github.com/SigNoz/signoz/pkg/version.version=${{ needs.prepare.outputs.version }}
-X github.com/SigNoz/signoz/pkg/version.variant=enterprise
-X github.com/SigNoz/signoz/pkg/version.hash=${{ needs.prepare.outputs.hash }}
-X github.com/SigNoz/signoz/pkg/version.time=${{ needs.prepare.outputs.time }}
-X github.com/SigNoz/signoz/pkg/version.branch=${{ needs.prepare.outputs.branch }}
-X github.com/SigNoz/signoz/ee/query-service/constants.ZeusURL=https://api.staging.signoz.cloud
-X github.com/SigNoz/signoz/ee/query-service/constants.LicenseSignozIo=https://license.staging.signoz.cloud/api/v1'
GO_CGO_ENABLED: 1
DOCKER_BASE_IMAGES: '{"alpine": "alpine:3.20.3"}'
DOCKER_DOCKERFILE_PATH: ./ee/query-service/Dockerfile.multi-arch
DOCKER_MANIFEST: true
DOCKER_PROVIDERS: gcp
staging:
if: ${{ contains(github.event.label.name, 'staging:') || github.event.ref == 'refs/heads/main' }}
uses: signoz/primus.workflows/.github/workflows/github-trigger.yaml@main
secrets: inherit
needs: [prepare, go-build]
with:
PRIMUS_REF: main
GITHUB_ENVIRONMENT: staging
GITHUB_SILENT: true
GITHUB_REPOSITORY_NAME: charts-saas-v3-staging
GITHUB_EVENT_NAME: releaser
GITHUB_EVENT_PAYLOAD: "{\"deployment\": \"${{ needs.prepare.outputs.deployment }}\", \"signoz_version\": \"${{ needs.prepare.outputs.version }}\"}"

View File

@@ -1,122 +0,0 @@
name: build
on:
push:
branches:
- main
tags:
- v*
jobs:
enterprise:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup
uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: setup-qemu
uses: docker/setup-qemu-action@v3
- name: setup-buildx
uses: docker/setup-buildx-action@v3
with:
version: latest
- name: docker-login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: create-env-file
run: |
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
echo 'SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
echo 'SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
echo 'SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
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
echo 'CUSTOMERIO_ID="${{ secrets.CUSTOMERIO_ID }}"' >> frontend/.env
echo 'CUSTOMERIO_SITE_ID="${{ secrets.CUSTOMERIO_SITE_ID }}"' >> frontend/.env
- name: github-ref-info
shell: bash
run: |
GH_REF=${{ github.ref }}
if [[ "${{ github.ref_type }}" == "tag" ]]; then
PREFIX="refs/tags/"
echo "GH_IS_TAG=true" >> $GITHUB_ENV
echo "GH_TAG=${GH_REF#$PREFIX}" >> $GITHUB_ENV
else
PREFIX="refs/heads/"
echo "GH_IS_TAG=false" >> $GITHUB_ENV
echo "GH_BRANCH_NAME=${GH_REF#$PREFIX}" >> $GITHUB_ENV
fi
- name: set-version
run: |
if [ '${{ env.GH_IS_TAG }}' == 'true' ]; then
echo "VERSION=${{ env.GH_TAG }}" >> $GITHUB_ENV
elif [ '${{ env.GH_BRANCH_NAME }}' == 'main' ]; then
echo "VERSION=latest" >> $GITHUB_ENV
else
echo "VERSION=${{ env.GH_BRANCH_NAME }}" >> $GITHUB_ENV
fi
- name: cross-compilation-tools
run: |
set -ex
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
- name: publish
run: make docker-buildx-enterprise
community:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: setup-go
uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: setup-qemu
uses: docker/setup-qemu-action@v3
- name: setup-buildx
uses: docker/setup-buildx-action@v3
with:
version: latest
- name: docker-login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: github-ref-info
shell: bash
run: |
GH_REF=${{ github.ref }}
if [[ "${{ github.ref_type }}" == "tag" ]]; then
PREFIX="refs/tags/"
echo "GH_IS_TAG=true" >> $GITHUB_ENV
echo "GH_TAG=${GH_REF#$PREFIX}" >> $GITHUB_ENV
else
PREFIX="refs/heads/"
echo "GH_IS_TAG=false" >> $GITHUB_ENV
echo "GH_BRANCH_NAME=${GH_REF#$PREFIX}" >> $GITHUB_ENV
fi
- name: set-version
run: |
if [ '${{ env.GH_IS_TAG }}' == 'true' ]; then
echo "VERSION=${{ env.GH_TAG }}" >> $GITHUB_ENV
elif [ '${{ env.GH_BRANCH_NAME }}' == 'main' ]; then
echo "VERSION=latest" >> $GITHUB_ENV
else
echo "VERSION=${{ env.GH_BRANCH_NAME }}" >> $GITHUB_ENV
fi
- name: cross-compilation-tools
run: |
set -ex
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
- name: publish
run: make docker-buildx-community

View File

@@ -36,12 +36,17 @@ jobs:
echo "GITHUB_BRANCH: ${GITHUB_BRANCH}" echo "GITHUB_BRANCH: ${GITHUB_BRANCH}"
echo "GITHUB_SHA: ${GITHUB_SHA}" echo "GITHUB_SHA: ${GITHUB_SHA}"
export VERSION="${GITHUB_SHA:0:7}" # needed for child process to access it export VERSION="${GITHUB_SHA:0:7}" # needed for child process to access it
export OTELCOL_TAG="main"
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
export KAFKA_SPAN_EVAL="true" export KAFKA_SPAN_EVAL="true"
docker system prune --force docker system prune --force --all
docker pull signoz/signoz-otel-collector:main OTELCOL_TAG=$(curl -s https://api.github.com/repos/SigNoz/signoz-otel-collector/releases/latest | jq -r '.tag_name // "not-found"')
docker pull signoz/signoz-schema-migrator:main if [[ "${OTELCOL_TAG}" == "not-found" ]]; then
echo "warning: unable to determine latest SigNoz OtelCollector release tag, skipping latest otelcol deployment"
else
export OTELCOL_TAG=${OTELCOL_TAG}
docker pull signoz/signoz-otel-collector:${OTELCOL_TAG}
docker pull signoz/signoz-schema-migrator:${OTELCOL_TAG}
fi
cd ~/signoz cd ~/signoz
git status git status
git add . git add .

View File

@@ -38,7 +38,7 @@ jobs:
export VERSION="${GITHUB_SHA:0:7}" # needed for child process to access it export VERSION="${GITHUB_SHA:0:7}" # needed for child process to access it
export DEV_BUILD="1" export DEV_BUILD="1"
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
docker system prune --force docker system prune --force --all
cd ~/signoz cd ~/signoz
git status git status
git add . git add .

1
.gitignore vendored
View File

@@ -54,6 +54,7 @@ ee/query-service/tests/test-deploy/data/
bin/ bin/
.local/ .local/
*/query-service/queries.active */query-service/queries.active
ee/query-service/db
# e2e # e2e

17
.versions/alpine Normal file
View File

@@ -0,0 +1,17 @@
#### Auto generated by make docker-version-alpine. DO NOT EDIT! ####
amd64=029a752048e32e843bd6defe3841186fb8d19a28dae8ec287f433bb9d6d1ad85
unknown=5fea95373b9ec85974843f31446fa6a9df4492dddae4e1cb056193c34a20a5be
arm=b4aef1a899e0271f06d948c9a8fa626ecdb2202d3a178bc14775dd559e23df8e
unknown=a4d1e27e63a9d6353046eb25a2f0ec02945012b217f4364cd83a73fe6dfb0b15
arm=4fdafe217d0922f3c3e2b4f64cf043f8403a4636685cd9c51fea2cbd1f419740
unknown=7f21ac2018d95b2c51a5779c1d5ca6c327504adc3b0fdc747a6725d30b3f13c2
arm64=ea3c5a9671f7b3f7eb47eab06f73bc6591df978b0d5955689a9e6f943aa368c0
unknown=a8ba68c1a9e6eea8041b4b8f996c235163440808b9654a865976fdcbede0f433
386=dea9f02e103e837849f984d5679305c758aba7fea1b95b7766218597f61a05ab
unknown=3c6629bec05c8273a927d46b77428bf4a378dad911a0ae284887becdc149b734
ppc64le=0880443bffa028dfbbc4094a32dd6b7ac25684e4c0a3d50da9e0acae355c5eaf
unknown=bb48308f976b266e3ab39bbf9af84521959bd9c295d3c763690cf41f8df2a626
riscv64=d76e6fbe348ff20c2931bb7f101e49379648e026de95dd37f96e00ce1909dcf7
unknown=dd807544365f6dc187cbe6de0806adce2ea9de3e7124717d1d8e8b7a18b77b64
s390x=b815fadf80495594eb6296a6af0bc647ae5f193e0044e07acec7e5b378c9ce2d
unknown=74681be74a280a88abb53ff1e048eb1fb624b30d0066730df6d8afd02ba82e01

View File

@@ -77,4 +77,4 @@ Need assistance? Join our Slack community:
## Where do I go from here? ## Where do I go from here?
- Set up your [development environment](docs/contributing/development.md) - Set up your [development environment](docs/contributing/development.md)
- Deploy and observe [SigNoz in action with OpenTelemetry Demo Application](docs/otel-demo/otel-demo-docs.md) - Deploy and observe [SigNoz in action with OpenTelemetry Demo Application](docs/otel-demo-docs.md)

View File

@@ -74,6 +74,10 @@ go-run-enterprise: ## Runs the enterprise go backend server
--use-logs-new-schema true \ --use-logs-new-schema true \
--use-trace-new-schema true --use-trace-new-schema true
.PHONY: go-test
go-test: ## Runs go unit tests
@go test -race ./...
.PHONY: go-run-community .PHONY: go-run-community
go-run-community: ## Runs the community go backend server go-run-community: ## Runs the community go backend server
@SIGNOZ_INSTRUMENTATION_LOGS_LEVEL=debug \ @SIGNOZ_INSTRUMENTATION_LOGS_LEVEL=debug \

View File

@@ -72,7 +72,6 @@ sqlstore:
# The path to the SQLite database file. # The path to the SQLite database file.
path: /var/lib/signoz/signoz.db path: /var/lib/signoz/signoz.db
##################### APIServer ##################### ##################### APIServer #####################
apiserver: apiserver:
timeout: timeout:
@@ -91,20 +90,29 @@ apiserver:
- /api/v1/version - /api/v1/version
- / - /
##################### TelemetryStore ##################### ##################### TelemetryStore #####################
telemetrystore: telemetrystore:
# Specifies the telemetrystore provider to use.
provider: clickhouse
# Maximum number of idle connections in the connection pool. # Maximum number of idle connections in the connection pool.
max_idle_conns: 50 max_idle_conns: 50
# Maximum number of open connections to the database. # Maximum number of open connections to the database.
max_open_conns: 100 max_open_conns: 100
# Maximum time to wait for a connection to be established. # Maximum time to wait for a connection to be established.
dial_timeout: 5s dial_timeout: 5s
# Specifies the telemetrystore provider to use.
provider: clickhouse
clickhouse: clickhouse:
# The DSN to use for ClickHouse. # The DSN to use for clickhouse.
dsn: http://localhost:9000 dsn: tcp://localhost:9000
##################### Prometheus #####################
prometheus:
active_query_tracker:
# Whether to enable the active query tracker.
enabled: true
# The path to use for the active query tracker.
path: ""
# The maximum number of concurrent queries.
max_concurrent: 20
##################### Alertmanager ##################### ##################### Alertmanager #####################
alertmanager: alertmanager:
@@ -117,7 +125,7 @@ alertmanager:
# The poll interval for periodically syncing the alertmanager with the config in the store. # The poll interval for periodically syncing the alertmanager with the config in the store.
poll_interval: 1m poll_interval: 1m
# The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself. # The URL under which Alertmanager is externally reachable (for example, if Alertmanager is served via a reverse proxy). Used for generating relative and absolute links back to Alertmanager itself.
external_url: http://localhost:9093 external_url: http://localhost:8080
# The global configuration for the alertmanager. All the exahustive fields can be found in the upstream: https://github.com/prometheus/alertmanager/blob/efa05feffd644ba4accb526e98a8c6545d26a783/config/config.go#L833 # The global configuration for the alertmanager. All the exahustive fields can be found in the upstream: https://github.com/prometheus/alertmanager/blob/efa05feffd644ba4accb526e98a8c6545d26a783/config/config.go#L833
global: global:
# ResolveTimeout is the time after which an alert is declared resolved if it has not been updated. # ResolveTimeout is the time after which an alert is declared resolved if it has not been updated.

View File

@@ -174,7 +174,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:v0.76.2 image: signoz/signoz:v0.78.1
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
- --use-logs-new-schema=true - --use-logs-new-schema=true
@@ -208,7 +208,7 @@ services:
retries: 3 retries: 3
otel-collector: otel-collector:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.111.34 image: signoz/signoz-otel-collector:v0.111.38
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml - --manager-config=/etc/manager-config.yaml
@@ -232,7 +232,7 @@ services:
- signoz - signoz
schema-migrator: schema-migrator:
!!merge <<: *common !!merge <<: *common
image: signoz/signoz-schema-migrator:v0.111.34 image: signoz/signoz-schema-migrator:v0.111.38
deploy: deploy:
restart_policy: restart_policy:
condition: on-failure condition: on-failure

View File

@@ -110,7 +110,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:v0.76.2 image: signoz/signoz:v0.78.1
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
- --use-logs-new-schema=true - --use-logs-new-schema=true
@@ -143,7 +143,7 @@ services:
retries: 3 retries: 3
otel-collector: otel-collector:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-otel-collector:v0.111.34 image: signoz/signoz-otel-collector:v0.111.38
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml
- --manager-config=/etc/manager-config.yaml - --manager-config=/etc/manager-config.yaml
@@ -167,7 +167,7 @@ services:
- signoz - signoz
schema-migrator: schema-migrator:
!!merge <<: *common !!merge <<: *common
image: signoz/signoz-schema-migrator:v0.111.34 image: signoz/signoz-schema-migrator:v0.111.38
deploy: deploy:
restart_policy: restart_policy:
condition: on-failure condition: on-failure

View File

@@ -177,7 +177,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.76.2} image: signoz/signoz:${VERSION:-v0.78.1}
container_name: signoz container_name: signoz
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
@@ -212,7 +212,7 @@ services:
# TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing? # TODO: support otel-collector multiple replicas. Nginx/Traefik for loadbalancing?
otel-collector: otel-collector:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.34} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.38}
container_name: signoz-otel-collector container_name: signoz-otel-collector
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml
@@ -238,7 +238,7 @@ services:
condition: service_healthy condition: service_healthy
schema-migrator-sync: schema-migrator-sync:
!!merge <<: *common !!merge <<: *common
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
container_name: schema-migrator-sync container_name: schema-migrator-sync
command: command:
- sync - sync
@@ -249,7 +249,7 @@ services:
condition: service_healthy condition: service_healthy
schema-migrator-async: schema-migrator-async:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
container_name: schema-migrator-async container_name: schema-migrator-async
command: command:
- async - async

View File

@@ -110,7 +110,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.76.2} image: signoz/signoz:${VERSION:-v0.78.1}
container_name: signoz container_name: signoz
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
@@ -146,7 +146,7 @@ services:
retries: 3 retries: 3
otel-collector: otel-collector:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.34} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.38}
container_name: signoz-otel-collector container_name: signoz-otel-collector
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml
@@ -168,7 +168,7 @@ services:
condition: service_healthy condition: service_healthy
schema-migrator-sync: schema-migrator-sync:
!!merge <<: *common !!merge <<: *common
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
container_name: schema-migrator-sync container_name: schema-migrator-sync
command: command:
- sync - sync
@@ -180,7 +180,7 @@ services:
restart: on-failure restart: on-failure
schema-migrator-async: schema-migrator-async:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
container_name: schema-migrator-async container_name: schema-migrator-async
command: command:
- async - async

View File

@@ -110,7 +110,7 @@ services:
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml # - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
signoz: signoz:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz:${VERSION:-v0.76.2} image: signoz/signoz:${VERSION:-v0.78.1}
container_name: signoz container_name: signoz
command: command:
- --config=/root/config/prometheus.yml - --config=/root/config/prometheus.yml
@@ -144,7 +144,7 @@ services:
retries: 3 retries: 3
otel-collector: otel-collector:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.34} image: signoz/signoz-otel-collector:${OTELCOL_TAG:-v0.111.38}
container_name: signoz-otel-collector container_name: signoz-otel-collector
command: command:
- --config=/etc/otel-collector-config.yaml - --config=/etc/otel-collector-config.yaml
@@ -166,7 +166,7 @@ services:
condition: service_healthy condition: service_healthy
schema-migrator-sync: schema-migrator-sync:
!!merge <<: *common !!merge <<: *common
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
container_name: schema-migrator-sync container_name: schema-migrator-sync
command: command:
- sync - sync
@@ -178,7 +178,7 @@ services:
restart: on-failure restart: on-failure
schema-migrator-async: schema-migrator-async:
!!merge <<: *db-depend !!merge <<: *db-depend
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.34} image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-v0.111.38}
container_name: schema-migrator-async container_name: schema-migrator-async
command: command:
- async - async

View File

@@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"time" "time"
eeTypes "github.com/SigNoz/signoz/ee/types"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
@@ -24,7 +25,7 @@ func (p *Pat) Wrap(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var values []string var values []string
var patToken string var patToken string
var pat types.StorablePersonalAccessToken var pat eeTypes.StorablePersonalAccessToken
for _, header := range p.headers { for _, header := range p.headers {
values = append(values, r.Header.Get(header)) values = append(values, r.Header.Get(header))

View File

@@ -18,4 +18,4 @@ COPY frontend/build/ /etc/signoz/web/
RUN chmod 755 /root /root/signoz RUN chmod 755 /root /root/signoz
ENTRYPOINT ["./signoz"] ENTRYPOINT ["./signoz"]
CMD ["-config", "/root/config/prometheus.yml"] CMD ["-config", "/root/config/prometheus.yml"]

View File

@@ -0,0 +1,22 @@
ARG ALPINE_SHA="pass-a-valid-docker-sha-otherwise-this-will-fail"
FROM alpine@sha256:${ALPINE_SHA}
LABEL maintainer="signoz"
WORKDIR /root
ARG OS="linux"
ARG ARCH
RUN apk update && \
apk add ca-certificates && \
rm -rf /var/cache/apk/*
COPY ./target/${OS}-${ARCH}/signoz /root/signoz
COPY ./conf/prometheus.yml /root/config/prometheus.yml
COPY ./templates/email /root/templates
COPY frontend/build/ /etc/signoz/web/
RUN chmod 755 /root /root/signoz
ENTRYPOINT ["./signoz"]
CMD ["-config", "/root/config/prometheus.yml"]

View File

@@ -28,11 +28,10 @@ func NewDailyProvider(opts ...GenericProviderOption[*DailyProvider]) *DailyProvi
} }
dp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{ dp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
Reader: dp.reader, Reader: dp.reader,
Cache: dp.cache, Cache: dp.cache,
KeyGenerator: queryBuilder.NewKeyGenerator(), KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: dp.fluxInterval, FluxInterval: dp.fluxInterval,
FeatureLookup: dp.ff,
}) })
return dp return dp

View File

@@ -28,11 +28,10 @@ func NewHourlyProvider(opts ...GenericProviderOption[*HourlyProvider]) *HourlyPr
} }
hp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{ hp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
Reader: hp.reader, Reader: hp.reader,
Cache: hp.cache, Cache: hp.cache,
KeyGenerator: queryBuilder.NewKeyGenerator(), KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: hp.fluxInterval, FluxInterval: hp.fluxInterval,
FeatureLookup: hp.ff,
}) })
return hp return hp

View File

@@ -38,12 +38,6 @@ func WithKeyGenerator[T BaseProvider](keyGenerator cache.KeyGenerator) GenericPr
} }
} }
func WithFeatureLookup[T BaseProvider](ff interfaces.FeatureLookup) GenericProviderOption[T] {
return func(p T) {
p.GetBaseSeasonalProvider().ff = ff
}
}
func WithReader[T BaseProvider](reader interfaces.Reader) GenericProviderOption[T] { func WithReader[T BaseProvider](reader interfaces.Reader) GenericProviderOption[T] {
return func(p T) { return func(p T) {
p.GetBaseSeasonalProvider().reader = reader p.GetBaseSeasonalProvider().reader = reader
@@ -56,7 +50,6 @@ type BaseSeasonalProvider struct {
fluxInterval time.Duration fluxInterval time.Duration
cache cache.Cache cache cache.Cache
keyGenerator cache.KeyGenerator keyGenerator cache.KeyGenerator
ff interfaces.FeatureLookup
} }
func (p *BaseSeasonalProvider) getQueryParams(req *GetAnomaliesRequest) *anomalyQueryParams { func (p *BaseSeasonalProvider) getQueryParams(req *GetAnomaliesRequest) *anomalyQueryParams {
@@ -313,6 +306,9 @@ func (p *BaseSeasonalProvider) getScore(
series, prevSeries, weekSeries, weekPrevSeries, past2SeasonSeries, past3SeasonSeries *v3.Series, value float64, idx int, series, prevSeries, weekSeries, weekPrevSeries, past2SeasonSeries, past3SeasonSeries *v3.Series, value float64, idx int,
) float64 { ) float64 {
expectedValue := p.getExpectedValue(series, prevSeries, weekSeries, weekPrevSeries, past2SeasonSeries, past3SeasonSeries, idx) expectedValue := p.getExpectedValue(series, prevSeries, weekSeries, weekPrevSeries, past2SeasonSeries, past3SeasonSeries, idx)
if expectedValue < 0 {
expectedValue = p.getMovingAvg(prevSeries, movingAvgWindowSize, idx)
}
return (value - expectedValue) / p.getStdDev(weekSeries) return (value - expectedValue) / p.getStdDev(weekSeries)
} }

View File

@@ -27,11 +27,10 @@ func NewWeeklyProvider(opts ...GenericProviderOption[*WeeklyProvider]) *WeeklyPr
} }
wp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{ wp.querierV2 = querierV2.NewQuerier(querierV2.QuerierOptions{
Reader: wp.reader, Reader: wp.reader,
Cache: wp.cache, Cache: wp.cache,
KeyGenerator: queryBuilder.NewKeyGenerator(), KeyGenerator: queryBuilder.NewKeyGenerator(),
FluxInterval: wp.fluxInterval, FluxInterval: wp.fluxInterval,
FeatureLookup: wp.ff,
}) })
return wp return wp

View File

@@ -11,6 +11,8 @@ import (
"github.com/SigNoz/signoz/ee/query-service/license" "github.com/SigNoz/signoz/ee/query-service/license"
"github.com/SigNoz/signoz/ee/query-service/usage" "github.com/SigNoz/signoz/ee/query-service/usage"
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/modules/preference"
preferencecore "github.com/SigNoz/signoz/pkg/modules/preference/core"
baseapp "github.com/SigNoz/signoz/pkg/query-service/app" baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations" "github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
"github.com/SigNoz/signoz/pkg/query-service/app/integrations" "github.com/SigNoz/signoz/pkg/query-service/app/integrations"
@@ -21,6 +23,7 @@ import (
rules "github.com/SigNoz/signoz/pkg/query-service/rules" rules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/signoz" "github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/preferencetypes"
"github.com/SigNoz/signoz/pkg/version" "github.com/SigNoz/signoz/pkg/version"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )
@@ -54,6 +57,7 @@ type APIHandler struct {
// NewAPIHandler returns an APIHandler // NewAPIHandler returns an APIHandler
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) { func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler, error) {
preference := preference.NewAPI(preferencecore.NewPreference(preferencecore.NewStore(signoz.SQLStore), preferencetypes.NewDefaultPreferenceMap()))
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{ baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector, Reader: opts.DataConnector,
@@ -71,6 +75,7 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
UseTraceNewSchema: opts.UseTraceNewSchema, UseTraceNewSchema: opts.UseTraceNewSchema,
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager), AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
Signoz: signoz, Signoz: signoz,
Preference: preference,
}) })
if err != nil { if err != nil {
@@ -157,7 +162,6 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(ah.getInvite)).Methods(http.MethodGet) router.HandleFunc("/api/v1/invite/{token}", am.OpenAccess(ah.getInvite)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/register", am.OpenAccess(ah.registerUser)).Methods(http.MethodPost) router.HandleFunc("/api/v1/register", am.OpenAccess(ah.registerUser)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost) router.HandleFunc("/api/v1/login", am.OpenAccess(ah.loginUser)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/traces/{traceId}", am.ViewAccess(ah.searchTraces)).Methods(http.MethodGet)
// PAT APIs // PAT APIs
router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost) router.HandleFunc("/api/v1/pats", am.AdminAccess(ah.createPAT)).Methods(http.MethodPost)

View File

@@ -11,7 +11,7 @@ import (
"time" "time"
"github.com/SigNoz/signoz/ee/query-service/constants" "github.com/SigNoz/signoz/ee/query-service/constants"
"github.com/SigNoz/signoz/ee/query-service/model" eeTypes "github.com/SigNoz/signoz/ee/types"
"github.com/SigNoz/signoz/pkg/query-service/auth" "github.com/SigNoz/signoz/pkg/query-service/auth"
baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants" baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/dao" "github.com/SigNoz/signoz/pkg/query-service/dao"
@@ -135,19 +135,12 @@ func (ah *APIHandler) getOrCreateCloudIntegrationPAT(ctx context.Context, orgId
zap.String("cloudProvider", cloudProvider), zap.String("cloudProvider", cloudProvider),
) )
newPAT := model.PAT{ newPAT := eeTypes.NewGettablePAT(
StorablePersonalAccessToken: types.StorablePersonalAccessToken{ integrationPATName,
Token: generatePATToken(), baseconstants.ViewerGroup,
UserID: integrationUser.ID, integrationUser.ID,
Name: integrationPATName, 0,
Role: baseconstants.ViewerGroup, )
ExpiresAt: 0,
TimeAuditable: types.TimeAuditable{
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
},
}
integrationPAT, err := ah.AppDao().CreatePAT(ctx, orgId, newPAT) integrationPAT, err := ah.AppDao().CreatePAT(ctx, orgId, newPAT)
if err != nil { if err != nil {
return "", basemodel.InternalError(fmt.Errorf( return "", basemodel.InternalError(fmt.Errorf(

View File

@@ -2,31 +2,24 @@ package api
import ( import (
"context" "context"
"crypto/rand"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"time" "time"
"github.com/SigNoz/signoz/ee/query-service/model" "github.com/SigNoz/signoz/ee/query-service/model"
"github.com/SigNoz/signoz/ee/types"
eeTypes "github.com/SigNoz/signoz/ee/types"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/http/render"
"github.com/SigNoz/signoz/pkg/query-service/auth" "github.com/SigNoz/signoz/pkg/query-service/auth"
baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants" baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model" basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/valuer"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"go.uber.org/zap" "go.uber.org/zap"
) )
func generatePATToken() string {
// Generate a 32-byte random token.
token := make([]byte, 32)
rand.Read(token)
// Encode the token in base64.
encodedToken := base64.StdEncoding.EncodeToString(token)
return encodedToken
}
func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) { func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
ctx := context.Background() ctx := context.Background()
@@ -43,31 +36,18 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
}, nil) }, nil)
return return
} }
pat := model.PAT{ pat := eeTypes.NewGettablePAT(
StorablePersonalAccessToken: types.StorablePersonalAccessToken{ req.Name,
Name: req.Name, req.Role,
Role: req.Role, user.ID,
ExpiresAt: req.ExpiresInDays, req.ExpiresInDays,
}, )
}
err = validatePATRequest(pat) err = validatePATRequest(pat)
if err != nil { if err != nil {
RespondError(w, model.BadRequest(err), nil) RespondError(w, model.BadRequest(err), nil)
return return
} }
// All the PATs are associated with the user creating the PAT.
pat.UserID = user.ID
pat.CreatedAt = time.Now()
pat.UpdatedAt = time.Now()
pat.LastUsed = 0
pat.Token = generatePATToken()
if pat.ExpiresAt != 0 {
// convert expiresAt to unix timestamp from days
pat.ExpiresAt = time.Now().Unix() + (pat.ExpiresAt * 24 * 60 * 60)
}
zap.L().Info("Got Create PAT request", zap.Any("pat", pat)) zap.L().Info("Got Create PAT request", zap.Any("pat", pat))
var apierr basemodel.BaseApiError var apierr basemodel.BaseApiError
if pat, apierr = ah.AppDao().CreatePAT(ctx, user.OrgID, pat); apierr != nil { if pat, apierr = ah.AppDao().CreatePAT(ctx, user.OrgID, pat); apierr != nil {
@@ -78,7 +58,7 @@ func (ah *APIHandler) createPAT(w http.ResponseWriter, r *http.Request) {
ah.Respond(w, &pat) ah.Respond(w, &pat)
} }
func validatePATRequest(req model.PAT) error { func validatePATRequest(req types.GettablePAT) error {
if req.Role == "" || (req.Role != baseconstants.ViewerGroup && req.Role != baseconstants.EditorGroup && req.Role != baseconstants.AdminGroup) { if req.Role == "" || (req.Role != baseconstants.ViewerGroup && req.Role != baseconstants.EditorGroup && req.Role != baseconstants.AdminGroup) {
return fmt.Errorf("valid role is required") return fmt.Errorf("valid role is required")
} }
@@ -94,7 +74,7 @@ func validatePATRequest(req model.PAT) error {
func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) { func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
ctx := context.Background() ctx := context.Background()
req := model.PAT{} req := types.GettablePAT{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil { if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
RespondError(w, model.BadRequest(err), nil) RespondError(w, model.BadRequest(err), nil)
return return
@@ -116,7 +96,12 @@ func (ah *APIHandler) updatePAT(w http.ResponseWriter, r *http.Request) {
} }
req.UpdatedByUserID = user.ID req.UpdatedByUserID = user.ID
id := mux.Vars(r)["id"] idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
req.UpdatedAt = time.Now() req.UpdatedAt = time.Now()
zap.L().Info("Got Update PAT request", zap.Any("pat", req)) zap.L().Info("Got Update PAT request", zap.Any("pat", req))
var apierr basemodel.BaseApiError var apierr basemodel.BaseApiError
@@ -149,7 +134,12 @@ func (ah *APIHandler) getPATs(w http.ResponseWriter, r *http.Request) {
func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) { func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
ctx := context.Background() ctx := context.Background()
id := mux.Vars(r)["id"] idStr := mux.Vars(r)["id"]
id, err := valuer.NewUUID(idStr)
if err != nil {
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "id is not a valid uuid-v7"))
return
}
user, err := auth.GetUserFromReqContext(r.Context()) user, err := auth.GetUserFromReqContext(r.Context())
if err != nil { if err != nil {
RespondError(w, &model.ApiError{ RespondError(w, &model.ApiError{
@@ -159,7 +149,7 @@ func (ah *APIHandler) revokePAT(w http.ResponseWriter, r *http.Request) {
return return
} }
zap.L().Info("Revoke PAT with id", zap.String("id", id)) zap.L().Info("Revoke PAT with id", zap.String("id", id.StringValue()))
if apierr := ah.AppDao().RevokePAT(ctx, user.OrgID, id, user.ID); apierr != nil { if apierr := ah.AppDao().RevokePAT(ctx, user.OrgID, id, user.ID); apierr != nil {
RespondError(w, apierr, nil) RespondError(w, apierr, nil)
return return

View File

@@ -88,28 +88,24 @@ func (aH *APIHandler) queryRangeV4(w http.ResponseWriter, r *http.Request) {
anomaly.WithCache[*anomaly.WeeklyProvider](aH.opts.Cache), anomaly.WithCache[*anomaly.WeeklyProvider](aH.opts.Cache),
anomaly.WithKeyGenerator[*anomaly.WeeklyProvider](queryBuilder.NewKeyGenerator()), anomaly.WithKeyGenerator[*anomaly.WeeklyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.WeeklyProvider](aH.opts.DataConnector), anomaly.WithReader[*anomaly.WeeklyProvider](aH.opts.DataConnector),
anomaly.WithFeatureLookup[*anomaly.WeeklyProvider](aH.opts.FeatureFlags),
) )
case anomaly.SeasonalityDaily: case anomaly.SeasonalityDaily:
provider = anomaly.NewDailyProvider( provider = anomaly.NewDailyProvider(
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache), anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()), anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector), anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
) )
case anomaly.SeasonalityHourly: case anomaly.SeasonalityHourly:
provider = anomaly.NewHourlyProvider( provider = anomaly.NewHourlyProvider(
anomaly.WithCache[*anomaly.HourlyProvider](aH.opts.Cache), anomaly.WithCache[*anomaly.HourlyProvider](aH.opts.Cache),
anomaly.WithKeyGenerator[*anomaly.HourlyProvider](queryBuilder.NewKeyGenerator()), anomaly.WithKeyGenerator[*anomaly.HourlyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.HourlyProvider](aH.opts.DataConnector), anomaly.WithReader[*anomaly.HourlyProvider](aH.opts.DataConnector),
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](aH.opts.FeatureFlags),
) )
default: default:
provider = anomaly.NewDailyProvider( provider = anomaly.NewDailyProvider(
anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache), anomaly.WithCache[*anomaly.DailyProvider](aH.opts.Cache),
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()), anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector), anomaly.WithReader[*anomaly.DailyProvider](aH.opts.DataConnector),
anomaly.WithFeatureLookup[*anomaly.DailyProvider](aH.opts.FeatureFlags),
) )
} }
anomalies, err := provider.GetAnomalies(r.Context(), &anomaly.GetAnomaliesRequest{Params: queryRangeParams}) anomalies, err := provider.GetAnomalies(r.Context(), &anomaly.GetAnomaliesRequest{Params: queryRangeParams})

View File

@@ -1,33 +0,0 @@
package api
import (
"net/http"
"github.com/SigNoz/signoz/ee/query-service/app/db"
"github.com/SigNoz/signoz/ee/query-service/model"
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"go.uber.org/zap"
)
func (ah *APIHandler) searchTraces(w http.ResponseWriter, r *http.Request) {
if !ah.CheckFeature(basemodel.SmartTraceDetail) {
zap.L().Info("SmartTraceDetail feature is not enabled in this plan")
ah.APIHandler.SearchTraces(w, r)
return
}
searchTracesParams, err := baseapp.ParseSearchTracesParams(r)
if err != nil {
RespondError(w, &model.ApiError{Typ: model.ErrorBadData, Err: err}, "Error reading params")
return
}
result, err := ah.opts.DataConnector.SearchTraces(r.Context(), searchTracesParams, db.SmartTraceAlgorithm)
if ah.HandleError(w, err, http.StatusBadRequest) {
return
}
ah.WriteJSON(w, r, result)
}

View File

@@ -5,38 +5,33 @@ import (
"github.com/ClickHouse/clickhouse-go/v2" "github.com/ClickHouse/clickhouse-go/v2"
"github.com/jmoiron/sqlx"
"github.com/SigNoz/signoz/pkg/cache" "github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/prometheus"
basechr "github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader" basechr "github.com/SigNoz/signoz/pkg/query-service/app/clickhouseReader"
"github.com/SigNoz/signoz/pkg/query-service/interfaces" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
) )
type ClickhouseReader struct { type ClickhouseReader struct {
conn clickhouse.Conn conn clickhouse.Conn
appdb *sqlx.DB appdb sqlstore.SQLStore
*basechr.ClickHouseReader *basechr.ClickHouseReader
} }
func NewDataConnector( func NewDataConnector(
localDB *sqlx.DB, sqlDB sqlstore.SQLStore,
ch clickhouse.Conn, telemetryStore telemetrystore.TelemetryStore,
promConfigPath string, prometheus prometheus.Prometheus,
lm interfaces.FeatureLookup,
cluster string, cluster string,
useLogsNewSchema bool, useLogsNewSchema bool,
useTraceNewSchema bool, useTraceNewSchema bool,
fluxIntervalForTraceDetail time.Duration, fluxIntervalForTraceDetail time.Duration,
cache cache.Cache, cache cache.Cache,
) *ClickhouseReader { ) *ClickhouseReader {
chReader := basechr.NewReader(localDB, ch, promConfigPath, lm, cluster, useLogsNewSchema, useTraceNewSchema, fluxIntervalForTraceDetail, cache) chReader := basechr.NewReader(sqlDB, telemetryStore, prometheus, cluster, useLogsNewSchema, useTraceNewSchema, fluxIntervalForTraceDetail, cache)
return &ClickhouseReader{ return &ClickhouseReader{
conn: ch, conn: telemetryStore.ClickhouseDB(),
appdb: localDB, appdb: sqlDB,
ClickHouseReader: chReader, ClickHouseReader: chReader,
} }
} }
func (r *ClickhouseReader) Start(readerReady chan bool) {
r.ClickHouseReader.Start(readerReady)
}

View File

@@ -18,13 +18,14 @@ import (
"github.com/SigNoz/signoz/ee/query-service/constants" "github.com/SigNoz/signoz/ee/query-service/constants"
"github.com/SigNoz/signoz/ee/query-service/dao" "github.com/SigNoz/signoz/ee/query-service/dao"
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway" "github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
"github.com/SigNoz/signoz/ee/query-service/interfaces"
"github.com/SigNoz/signoz/ee/query-service/rules" "github.com/SigNoz/signoz/ee/query-service/rules"
"github.com/SigNoz/signoz/pkg/alertmanager" "github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/http/middleware" "github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/query-service/auth" "github.com/SigNoz/signoz/pkg/query-service/auth"
"github.com/SigNoz/signoz/pkg/signoz" "github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlstore" "github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types" "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/web" "github.com/SigNoz/signoz/pkg/web"
@@ -43,13 +44,11 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline" "github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"github.com/SigNoz/signoz/pkg/query-service/app/opamp" "github.com/SigNoz/signoz/pkg/query-service/app/opamp"
opAmpModel "github.com/SigNoz/signoz/pkg/query-service/app/opamp/model" opAmpModel "github.com/SigNoz/signoz/pkg/query-service/app/opamp/model"
"github.com/SigNoz/signoz/pkg/query-service/app/preferences"
"github.com/SigNoz/signoz/pkg/query-service/cache" "github.com/SigNoz/signoz/pkg/query-service/cache"
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants" baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/healthcheck" "github.com/SigNoz/signoz/pkg/query-service/healthcheck"
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces" baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model" basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
pqle "github.com/SigNoz/signoz/pkg/query-service/pqlEngine"
baserules "github.com/SigNoz/signoz/pkg/query-service/rules" baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/query-service/telemetry" "github.com/SigNoz/signoz/pkg/query-service/telemetry"
"github.com/SigNoz/signoz/pkg/query-service/utils" "github.com/SigNoz/signoz/pkg/query-service/utils"
@@ -116,10 +115,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
return nil, err return nil, err
} }
if err := preferences.InitDB(serverOptions.SigNoz.SQLStore.SQLxDB()); err != nil {
return nil, err
}
if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore); err != nil { if err := dashboards.InitDB(serverOptions.SigNoz.SQLStore); err != nil {
return nil, err return nil, err
} }
@@ -137,27 +132,22 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
// set license manager as feature flag provider in dao // set license manager as feature flag provider in dao
modelDao.SetFlagProvider(lm) modelDao.SetFlagProvider(lm)
readerReady := make(chan bool)
fluxIntervalForTraceDetail, err := time.ParseDuration(serverOptions.FluxIntervalForTraceDetail) fluxIntervalForTraceDetail, err := time.ParseDuration(serverOptions.FluxIntervalForTraceDetail)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var reader interfaces.DataConnector reader := db.NewDataConnector(
qb := db.NewDataConnector( serverOptions.SigNoz.SQLStore,
serverOptions.SigNoz.SQLStore.SQLxDB(), serverOptions.SigNoz.TelemetryStore,
serverOptions.SigNoz.TelemetryStore.ClickHouseDB(), serverOptions.SigNoz.Prometheus,
serverOptions.PromConfigPath,
lm,
serverOptions.Cluster, serverOptions.Cluster,
serverOptions.UseLogsNewSchema, serverOptions.UseLogsNewSchema,
serverOptions.UseTraceNewSchema, serverOptions.UseTraceNewSchema,
fluxIntervalForTraceDetail, fluxIntervalForTraceDetail,
serverOptions.SigNoz.Cache, serverOptions.SigNoz.Cache,
) )
go qb.Start(readerReady)
reader = qb
skipConfig := &basemodel.SkipConfig{} skipConfig := &basemodel.SkipConfig{}
if serverOptions.SkipTopLvlOpsPath != "" { if serverOptions.SkipTopLvlOpsPath != "" {
@@ -176,19 +166,18 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
c = cache.NewCache(cacheOpts) c = cache.NewCache(cacheOpts)
} }
<-readerReady
rm, err := makeRulesManager( rm, err := makeRulesManager(
serverOptions.PromConfigPath,
serverOptions.RuleRepoURL, serverOptions.RuleRepoURL,
serverOptions.SigNoz.SQLStore.SQLxDB(), serverOptions.SigNoz.SQLStore.SQLxDB(),
reader, reader,
c, c,
serverOptions.DisableRules, serverOptions.DisableRules,
lm,
serverOptions.UseLogsNewSchema, serverOptions.UseLogsNewSchema,
serverOptions.UseTraceNewSchema, serverOptions.UseTraceNewSchema,
serverOptions.SigNoz.Alertmanager, serverOptions.SigNoz.Alertmanager,
serverOptions.SigNoz.SQLStore, serverOptions.SigNoz.SQLStore,
serverOptions.SigNoz.TelemetryStore,
serverOptions.SigNoz.Prometheus,
) )
if err != nil { if err != nil {
@@ -233,7 +222,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
} }
// start the usagemanager // start the usagemanager
usageManager, err := usage.New(modelDao, lm.GetRepo(), serverOptions.SigNoz.TelemetryStore.ClickHouseDB(), serverOptions.Config.TelemetryStore.ClickHouse.DSN) usageManager, err := usage.New(modelDao, lm.GetRepo(), serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.Config.TelemetryStore.Clickhouse.DSN)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -304,7 +293,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
&opAmpModel.AllAgents, agentConfMgr, &opAmpModel.AllAgents, agentConfMgr,
) )
errorList := qb.PreloadMetricsMetadata(context.Background()) errorList := reader.PreloadMetricsMetadata(context.Background())
for _, er := range errorList { for _, er := range errorList {
zap.L().Error("failed to preload metrics metadata", zap.Error(er)) zap.L().Error("failed to preload metrics metadata", zap.Error(er))
} }
@@ -537,33 +526,27 @@ func (s *Server) Stop() error {
} }
func makeRulesManager( func makeRulesManager(
promConfigPath,
ruleRepoURL string, ruleRepoURL string,
db *sqlx.DB, db *sqlx.DB,
ch baseint.Reader, ch baseint.Reader,
cache cache.Cache, cache cache.Cache,
disableRules bool, disableRules bool,
fm baseint.FeatureLookup,
useLogsNewSchema bool, useLogsNewSchema bool,
useTraceNewSchema bool, useTraceNewSchema bool,
alertmanager alertmanager.Alertmanager, alertmanager alertmanager.Alertmanager,
sqlstore sqlstore.SQLStore, sqlstore sqlstore.SQLStore,
telemetryStore telemetrystore.TelemetryStore,
prometheus prometheus.Prometheus,
) (*baserules.Manager, error) { ) (*baserules.Manager, error) {
// create engine
pqle, err := pqle.FromConfigPath(promConfigPath)
if err != nil {
return nil, fmt.Errorf("failed to create pql engine : %v", err)
}
// create manager opts // create manager opts
managerOpts := &baserules.ManagerOptions{ managerOpts := &baserules.ManagerOptions{
PqlEngine: pqle, TelemetryStore: telemetryStore,
Prometheus: prometheus,
RepoURL: ruleRepoURL, RepoURL: ruleRepoURL,
DBConn: db, DBConn: db,
Context: context.Background(), Context: context.Background(),
Logger: zap.L(), Logger: zap.L(),
DisableRules: disableRules, DisableRules: disableRules,
FeatureFlags: fm,
Reader: ch, Reader: ch,
Cache: cache, Cache: cache,
EvalDelay: baseconst.GetEvalDelay(), EvalDelay: baseconst.GetEvalDelay(),

View File

@@ -4,13 +4,13 @@ import (
"context" "context"
"net/url" "net/url"
"github.com/SigNoz/signoz/ee/query-service/model"
"github.com/SigNoz/signoz/ee/types" "github.com/SigNoz/signoz/ee/types"
basedao "github.com/SigNoz/signoz/pkg/query-service/dao" basedao "github.com/SigNoz/signoz/pkg/query-service/dao"
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces" baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model" basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
ossTypes "github.com/SigNoz/signoz/pkg/types" ossTypes "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
@@ -36,11 +36,11 @@ type ModelDao interface {
DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError DeleteDomain(ctx context.Context, id uuid.UUID) basemodel.BaseApiError
GetDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, basemodel.BaseApiError) GetDomainByEmail(ctx context.Context, email string) (*types.GettableOrgDomain, basemodel.BaseApiError)
CreatePAT(ctx context.Context, orgID string, p model.PAT) (model.PAT, basemodel.BaseApiError) CreatePAT(ctx context.Context, orgID string, p types.GettablePAT) (types.GettablePAT, basemodel.BaseApiError)
UpdatePAT(ctx context.Context, orgID string, p model.PAT, id string) basemodel.BaseApiError UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id valuer.UUID) basemodel.BaseApiError
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError) GetPAT(ctx context.Context, pat string) (*types.GettablePAT, basemodel.BaseApiError)
GetPATByID(ctx context.Context, orgID string, id string) (*model.PAT, basemodel.BaseApiError) GetPATByID(ctx context.Context, orgID string, id valuer.UUID) (*types.GettablePAT, basemodel.BaseApiError)
GetUserByPAT(ctx context.Context, orgID string, token string) (*ossTypes.GettableUser, basemodel.BaseApiError) GetUserByPAT(ctx context.Context, orgID string, token string) (*ossTypes.GettableUser, basemodel.BaseApiError)
ListPATs(ctx context.Context, orgID string) ([]model.PAT, basemodel.BaseApiError) ListPATs(ctx context.Context, orgID string) ([]types.GettablePAT, basemodel.BaseApiError)
RevokePAT(ctx context.Context, orgID string, id string, userID string) basemodel.BaseApiError RevokePAT(ctx context.Context, orgID string, id valuer.UUID, userID string) basemodel.BaseApiError
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"net/url" "net/url"
"strings"
"time" "time"
"github.com/SigNoz/signoz/ee/query-service/constants" "github.com/SigNoz/signoz/ee/query-service/constants"
@@ -162,12 +161,7 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
// find domain from email // find domain from email
orgDomain, apierr := m.GetDomainByEmail(ctx, email) orgDomain, apierr := m.GetDomainByEmail(ctx, email)
if apierr != nil { if apierr != nil {
var emailDomain string zap.L().Error("failed to get org domain from email", zap.String("email", email), zap.Error(apierr.ToError()))
emailComponents := strings.Split(email, "@")
if len(emailComponents) > 0 {
emailDomain = emailComponents[1]
}
zap.L().Error("failed to get org domain from email", zap.String("emailDomain", emailDomain), zap.Error(apierr.ToError()))
return resp, apierr return resp, apierr
} }

View File

@@ -6,45 +6,53 @@ import (
"time" "time"
"github.com/SigNoz/signoz/ee/query-service/model" "github.com/SigNoz/signoz/ee/query-service/model"
"github.com/SigNoz/signoz/ee/types"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model" basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
"github.com/SigNoz/signoz/pkg/types" ossTypes "github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"go.uber.org/zap" "go.uber.org/zap"
) )
func (m *modelDao) CreatePAT(ctx context.Context, orgID string, p model.PAT) (model.PAT, basemodel.BaseApiError) { func (m *modelDao) CreatePAT(ctx context.Context, orgID string, p types.GettablePAT) (types.GettablePAT, basemodel.BaseApiError) {
p.StorablePersonalAccessToken.OrgID = orgID p.StorablePersonalAccessToken.OrgID = orgID
p.StorablePersonalAccessToken.ID = valuer.GenerateUUID()
_, err := m.DB().NewInsert(). _, err := m.DB().NewInsert().
Model(&p.StorablePersonalAccessToken). Model(&p.StorablePersonalAccessToken).
Returning("id").
Exec(ctx) Exec(ctx)
if err != nil { if err != nil {
zap.L().Error("Failed to insert PAT in db, err: %v", zap.Error(err)) zap.L().Error("Failed to insert PAT in db, err: %v", zap.Error(err))
return model.PAT{}, model.InternalError(fmt.Errorf("PAT insertion failed")) return types.GettablePAT{}, model.InternalError(fmt.Errorf("PAT insertion failed"))
} }
createdByUser, _ := m.GetUser(ctx, p.UserID) createdByUser, _ := m.GetUser(ctx, p.UserID)
if createdByUser == nil { if createdByUser == nil {
p.CreatedByUser = model.User{ p.CreatedByUser = types.PatUser{
NotFound: true, NotFound: true,
} }
} else { } else {
p.CreatedByUser = model.User{ p.CreatedByUser = types.PatUser{
Id: createdByUser.ID, User: ossTypes.User{
Name: createdByUser.Name, ID: createdByUser.ID,
Email: createdByUser.Email, Name: createdByUser.Name,
CreatedAt: createdByUser.CreatedAt.Unix(), Email: createdByUser.Email,
ProfilePictureURL: createdByUser.ProfilePictureURL, TimeAuditable: ossTypes.TimeAuditable{
NotFound: false, CreatedAt: createdByUser.CreatedAt,
UpdatedAt: createdByUser.UpdatedAt,
},
ProfilePictureURL: createdByUser.ProfilePictureURL,
},
NotFound: false,
} }
} }
return p, nil return p, nil
} }
func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p model.PAT, id string) basemodel.BaseApiError { func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p types.GettablePAT, id valuer.UUID) basemodel.BaseApiError {
_, err := m.DB().NewUpdate(). _, err := m.DB().NewUpdate().
Model(&p.StorablePersonalAccessToken). Model(&p.StorablePersonalAccessToken).
Column("role", "name", "updated_at", "updated_by_user_id"). Column("role", "name", "updated_at", "updated_by_user_id").
Where("id = ?", id). Where("id = ?", id.StringValue()).
Where("org_id = ?", orgID). Where("org_id = ?", orgID).
Where("revoked = false"). Where("revoked = false").
Exec(ctx) Exec(ctx)
@@ -55,7 +63,7 @@ func (m *modelDao) UpdatePAT(ctx context.Context, orgID string, p model.PAT, id
return nil return nil
} }
func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]model.PAT, basemodel.BaseApiError) { func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]types.GettablePAT, basemodel.BaseApiError) {
pats := []types.StorablePersonalAccessToken{} pats := []types.StorablePersonalAccessToken{}
if err := m.DB().NewSelect(). if err := m.DB().NewSelect().
@@ -68,41 +76,51 @@ func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]model.PAT, bas
return nil, model.InternalError(fmt.Errorf("failed to fetch PATs")) return nil, model.InternalError(fmt.Errorf("failed to fetch PATs"))
} }
patsWithUsers := []model.PAT{} patsWithUsers := []types.GettablePAT{}
for i := range pats { for i := range pats {
patWithUser := model.PAT{ patWithUser := types.GettablePAT{
StorablePersonalAccessToken: pats[i], StorablePersonalAccessToken: pats[i],
} }
createdByUser, _ := m.GetUser(ctx, pats[i].UserID) createdByUser, _ := m.GetUser(ctx, pats[i].UserID)
if createdByUser == nil { if createdByUser == nil {
patWithUser.CreatedByUser = model.User{ patWithUser.CreatedByUser = types.PatUser{
NotFound: true, NotFound: true,
} }
} else { } else {
patWithUser.CreatedByUser = model.User{ patWithUser.CreatedByUser = types.PatUser{
Id: createdByUser.ID, User: ossTypes.User{
Name: createdByUser.Name, ID: createdByUser.ID,
Email: createdByUser.Email, Name: createdByUser.Name,
CreatedAt: createdByUser.CreatedAt.Unix(), Email: createdByUser.Email,
ProfilePictureURL: createdByUser.ProfilePictureURL, TimeAuditable: ossTypes.TimeAuditable{
NotFound: false, CreatedAt: createdByUser.CreatedAt,
UpdatedAt: createdByUser.UpdatedAt,
},
ProfilePictureURL: createdByUser.ProfilePictureURL,
},
NotFound: false,
} }
} }
updatedByUser, _ := m.GetUser(ctx, pats[i].UpdatedByUserID) updatedByUser, _ := m.GetUser(ctx, pats[i].UpdatedByUserID)
if updatedByUser == nil { if updatedByUser == nil {
patWithUser.UpdatedByUser = model.User{ patWithUser.UpdatedByUser = types.PatUser{
NotFound: true, NotFound: true,
} }
} else { } else {
patWithUser.UpdatedByUser = model.User{ patWithUser.UpdatedByUser = types.PatUser{
Id: updatedByUser.ID, User: ossTypes.User{
Name: updatedByUser.Name, ID: updatedByUser.ID,
Email: updatedByUser.Email, Name: updatedByUser.Name,
CreatedAt: updatedByUser.CreatedAt.Unix(), Email: updatedByUser.Email,
ProfilePictureURL: updatedByUser.ProfilePictureURL, TimeAuditable: ossTypes.TimeAuditable{
NotFound: false, CreatedAt: updatedByUser.CreatedAt,
UpdatedAt: updatedByUser.UpdatedAt,
},
ProfilePictureURL: updatedByUser.ProfilePictureURL,
},
NotFound: false,
} }
} }
@@ -111,14 +129,14 @@ func (m *modelDao) ListPATs(ctx context.Context, orgID string) ([]model.PAT, bas
return patsWithUsers, nil return patsWithUsers, nil
} }
func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id string, userID string) basemodel.BaseApiError { func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id valuer.UUID, userID string) basemodel.BaseApiError {
updatedAt := time.Now().Unix() updatedAt := time.Now().Unix()
_, err := m.DB().NewUpdate(). _, err := m.DB().NewUpdate().
Model(&types.StorablePersonalAccessToken{}). Model(&types.StorablePersonalAccessToken{}).
Set("revoked = ?", true). Set("revoked = ?", true).
Set("updated_by_user_id = ?", userID). Set("updated_by_user_id = ?", userID).
Set("updated_at = ?", updatedAt). Set("updated_at = ?", updatedAt).
Where("id = ?", id). Where("id = ?", id.StringValue()).
Where("org_id = ?", orgID). Where("org_id = ?", orgID).
Exec(ctx) Exec(ctx)
if err != nil { if err != nil {
@@ -128,7 +146,7 @@ func (m *modelDao) RevokePAT(ctx context.Context, orgID string, id string, userI
return nil return nil
} }
func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemodel.BaseApiError) { func (m *modelDao) GetPAT(ctx context.Context, token string) (*types.GettablePAT, basemodel.BaseApiError) {
pats := []types.StorablePersonalAccessToken{} pats := []types.StorablePersonalAccessToken{}
if err := m.DB().NewSelect(). if err := m.DB().NewSelect().
@@ -146,19 +164,19 @@ func (m *modelDao) GetPAT(ctx context.Context, token string) (*model.PAT, basemo
} }
} }
patWithUser := model.PAT{ patWithUser := types.GettablePAT{
StorablePersonalAccessToken: pats[0], StorablePersonalAccessToken: pats[0],
} }
return &patWithUser, nil return &patWithUser, nil
} }
func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*model.PAT, basemodel.BaseApiError) { func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id valuer.UUID) (*types.GettablePAT, basemodel.BaseApiError) {
pats := []types.StorablePersonalAccessToken{} pats := []types.StorablePersonalAccessToken{}
if err := m.DB().NewSelect(). if err := m.DB().NewSelect().
Model(&pats). Model(&pats).
Where("id = ?", id). Where("id = ?", id.StringValue()).
Where("org_id = ?", orgID). Where("org_id = ?", orgID).
Where("revoked = false"). Where("revoked = false").
Scan(ctx); err != nil { Scan(ctx); err != nil {
@@ -172,7 +190,7 @@ func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*mo
} }
} }
patWithUser := model.PAT{ patWithUser := types.GettablePAT{
StorablePersonalAccessToken: pats[0], StorablePersonalAccessToken: pats[0],
} }
@@ -180,8 +198,8 @@ func (m *modelDao) GetPATByID(ctx context.Context, orgID string, id string) (*mo
} }
// deprecated // deprecated
func (m *modelDao) GetUserByPAT(ctx context.Context, orgID string, token string) (*types.GettableUser, basemodel.BaseApiError) { func (m *modelDao) GetUserByPAT(ctx context.Context, orgID string, token string) (*ossTypes.GettableUser, basemodel.BaseApiError) {
users := []types.GettableUser{} users := []ossTypes.GettableUser{}
if err := m.DB().NewSelect(). if err := m.DB().NewSelect().
Model(&users). Model(&users).

View File

@@ -7,6 +7,5 @@ import (
// Connector defines methods for interaction // Connector defines methods for interaction
// with o11y data. for example - clickhouse // with o11y data. for example - clickhouse
type DataConnector interface { type DataConnector interface {
Start(readerReady chan bool)
baseint.Reader baseint.Reader
} }

View File

@@ -7,17 +7,17 @@ import (
"time" "time"
"github.com/SigNoz/signoz/ee/query-service/app" "github.com/SigNoz/signoz/ee/query-service/app"
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
"github.com/SigNoz/signoz/pkg/config" "github.com/SigNoz/signoz/pkg/config"
"github.com/SigNoz/signoz/pkg/config/envprovider" "github.com/SigNoz/signoz/pkg/config/envprovider"
"github.com/SigNoz/signoz/pkg/config/fileprovider" "github.com/SigNoz/signoz/pkg/config/fileprovider"
"github.com/SigNoz/signoz/pkg/query-service/auth" "github.com/SigNoz/signoz/pkg/query-service/auth"
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants" baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/signoz" "github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
"github.com/SigNoz/signoz/pkg/types/authtypes" "github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/version" "github.com/SigNoz/signoz/pkg/version"
prommodel "github.com/prometheus/common/model"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
) )
@@ -30,10 +30,6 @@ func initZapLog() *zap.Logger {
return logger return logger
} }
func init() {
prommodel.NameValidationScheme = prommodel.UTF8Validation
}
func main() { func main() {
var promConfigPath, skipTopLvlOpsPath string var promConfigPath, skipTopLvlOpsPath string
@@ -87,6 +83,7 @@ func main() {
MaxIdleConns: maxIdleConns, MaxIdleConns: maxIdleConns,
MaxOpenConns: maxOpenConns, MaxOpenConns: maxOpenConns,
DialTimeout: dialTimeout, DialTimeout: dialTimeout,
Config: promConfigPath,
}) })
if err != nil { if err != nil {
zap.L().Fatal("Failed to create config", zap.Error(err)) zap.L().Fatal("Failed to create config", zap.Error(err))
@@ -94,16 +91,21 @@ func main() {
version.Info.PrettyPrint(config.Version) version.Info.PrettyPrint(config.Version)
sqlStoreFactories := signoz.NewSQLStoreProviderFactories()
if err := sqlStoreFactories.Add(postgressqlstore.NewFactory(sqlstorehook.NewLoggingFactory())); err != nil {
zap.L().Fatal("Failed to add postgressqlstore factory", zap.Error(err))
}
signoz, err := signoz.New( signoz, err := signoz.New(
context.Background(), context.Background(),
config, config,
signoz.NewCacheProviderFactories(), signoz.NewCacheProviderFactories(),
signoz.NewWebProviderFactories(), signoz.NewWebProviderFactories(),
signoz.NewSQLStoreProviderFactories(), sqlStoreFactories,
signoz.NewTelemetryStoreProviderFactories(), signoz.NewTelemetryStoreProviderFactories(),
) )
if err != nil { if err != nil {
zap.L().Fatal("Failed to create signoz struct", zap.Error(err)) zap.L().Fatal("Failed to create signoz", zap.Error(err))
} }
jwtSecret := os.Getenv("SIGNOZ_JWT_SECRET") jwtSecret := os.Getenv("SIGNOZ_JWT_SECRET")

View File

@@ -157,8 +157,6 @@ func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
} }
switch planName { switch planName {
case PlanNameTeams:
features = append(features, ProPlan...)
case PlanNameEnterprise: case PlanNameEnterprise:
features = append(features, EnterprisePlan...) features = append(features, EnterprisePlan...)
case PlanNameBasic: case PlanNameBasic:

View File

@@ -74,21 +74,21 @@ func TestNewLicenseV3(t *testing.T) {
}, },
{ {
name: "Parse the entire license properly", name: "Parse the entire license properly",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`), data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1}`),
pass: true, pass: true,
expected: &LicenseV3{ expected: &LicenseV3{
ID: "does-not-matter", ID: "does-not-matter",
Key: "does-not-matter-key", Key: "does-not-matter-key",
Data: map[string]interface{}{ Data: map[string]interface{}{
"plan": map[string]interface{}{ "plan": map[string]interface{}{
"name": "TEAMS", "name": "ENTERPRISE",
}, },
"category": "FREE", "category": "FREE",
"status": "ACTIVE", "status": "ACTIVE",
"valid_from": float64(1730899309), "valid_from": float64(1730899309),
"valid_until": float64(-1), "valid_until": float64(-1),
}, },
PlanName: PlanNameTeams, PlanName: PlanNameEnterprise,
ValidFrom: 1730899309, ValidFrom: 1730899309,
ValidUntil: -1, ValidUntil: -1,
Status: "ACTIVE", Status: "ACTIVE",
@@ -98,14 +98,14 @@ func TestNewLicenseV3(t *testing.T) {
}, },
{ {
name: "Fallback to basic plan if license status is invalid", name: "Fallback to basic plan if license status is invalid",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"TEAMS"},"valid_from": 1730899309,"valid_until": -1}`), data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1}`),
pass: true, pass: true,
expected: &LicenseV3{ expected: &LicenseV3{
ID: "does-not-matter", ID: "does-not-matter",
Key: "does-not-matter-key", Key: "does-not-matter-key",
Data: map[string]interface{}{ Data: map[string]interface{}{
"plan": map[string]interface{}{ "plan": map[string]interface{}{
"name": "TEAMS", "name": "ENTERPRISE",
}, },
"category": "FREE", "category": "FREE",
"status": "INVALID", "status": "INVALID",
@@ -122,21 +122,21 @@ func TestNewLicenseV3(t *testing.T) {
}, },
{ {
name: "fallback states for validFrom and validUntil", name: "fallback states for validFrom and validUntil",
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"TEAMS"},"valid_from":1234.456,"valid_until":5678.567}`), data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from":1234.456,"valid_until":5678.567}`),
pass: true, pass: true,
expected: &LicenseV3{ expected: &LicenseV3{
ID: "does-not-matter", ID: "does-not-matter",
Key: "does-not-matter-key", Key: "does-not-matter-key",
Data: map[string]interface{}{ Data: map[string]interface{}{
"plan": map[string]interface{}{ "plan": map[string]interface{}{
"name": "TEAMS", "name": "ENTERPRISE",
}, },
"valid_from": 1234.456, "valid_from": 1234.456,
"valid_until": 5678.567, "valid_until": 5678.567,
"category": "FREE", "category": "FREE",
"status": "ACTIVE", "status": "ACTIVE",
}, },
PlanName: PlanNameTeams, PlanName: PlanNameEnterprise,
ValidFrom: 1234, ValidFrom: 1234,
ValidUntil: 5678, ValidUntil: 5678,
Status: "ACTIVE", Status: "ACTIVE",

View File

@@ -1,25 +1,7 @@
package model package model
import "github.com/SigNoz/signoz/pkg/types"
type User struct {
Id string `json:"id" db:"id"`
Name string `json:"name" db:"name"`
Email string `json:"email" db:"email"`
CreatedAt int64 `json:"createdAt" db:"created_at"`
ProfilePictureURL string `json:"profilePictureURL" db:"profile_picture_url"`
NotFound bool `json:"notFound"`
}
type CreatePATRequestBody struct { type CreatePATRequestBody struct {
Name string `json:"name"` Name string `json:"name"`
Role string `json:"role"` Role string `json:"role"`
ExpiresInDays int64 `json:"expiresInDays"` ExpiresInDays int64 `json:"expiresInDays"`
} }
type PAT struct {
CreatedByUser User `json:"createdByUser"`
UpdatedByUser User `json:"updatedByUser"`
types.StorablePersonalAccessToken
}

View File

@@ -1,30 +1,26 @@
package model package model
import ( import (
"github.com/SigNoz/signoz/pkg/query-service/constants"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model" basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
) )
const SSO = "SSO" const SSO = "SSO"
const Basic = "BASIC_PLAN" const Basic = "BASIC_PLAN"
const Pro = "PRO_PLAN"
const Enterprise = "ENTERPRISE_PLAN" const Enterprise = "ENTERPRISE_PLAN"
var ( var (
PlanNameEnterprise = "ENTERPRISE" PlanNameEnterprise = "ENTERPRISE"
PlanNameTeams = "TEAMS"
PlanNameBasic = "BASIC" PlanNameBasic = "BASIC"
) )
var ( var (
MapOldPlanKeyToNewPlanName map[string]string = map[string]string{PlanNameBasic: Basic, PlanNameTeams: Pro, PlanNameEnterprise: Enterprise} MapOldPlanKeyToNewPlanName map[string]string = map[string]string{PlanNameBasic: Basic, PlanNameEnterprise: Enterprise}
) )
var ( var (
LicenseStatusInvalid = "INVALID" LicenseStatusInvalid = "INVALID"
) )
const DisableUpsell = "DISABLE_UPSELL"
const Onboarding = "ONBOARDING" const Onboarding = "ONBOARDING"
const ChatSupport = "CHAT_SUPPORT" const ChatSupport = "CHAT_SUPPORT"
const Gateway = "GATEWAY" const Gateway = "GATEWAY"
@@ -38,90 +34,6 @@ var BasicPlan = basemodel.FeatureSet{
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
basemodel.Feature{
Name: basemodel.OSS,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: DisableUpsell,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.SmartTraceDetail,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.CustomMetricsFunction,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelEmail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelMsTeams,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{ basemodel.Feature{
Name: basemodel.UseSpanMetrics, Name: basemodel.UseSpanMetrics,
Active: false, Active: false,
@@ -151,134 +63,12 @@ var BasicPlan = basemodel.FeatureSet{
Route: "", Route: "",
}, },
basemodel.Feature{ basemodel.Feature{
Name: basemodel.HostsInfraMonitoring, Name: basemodel.TraceFunnels,
Active: constants.EnableHostsInfraMonitoring(),
Usage: 0,
UsageLimit: -1,
Route: "",
},
}
var ProPlan = basemodel.FeatureSet{
basemodel.Feature{
Name: SSO,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.OSS,
Active: false, Active: false,
Usage: 0, Usage: 0,
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
basemodel.Feature{
Name: basemodel.SmartTraceDetail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.CustomMetricsFunction,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelEmail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelMsTeams,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.UseSpanMetrics,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: Gateway,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: PremiumSupport,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AnomalyDetection,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.HostsInfraMonitoring,
Active: constants.EnableHostsInfraMonitoring(),
Usage: 0,
UsageLimit: -1,
Route: "",
},
} }
var EnterprisePlan = basemodel.FeatureSet{ var EnterprisePlan = basemodel.FeatureSet{
@@ -289,83 +79,6 @@ var EnterprisePlan = basemodel.FeatureSet{
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",
}, },
basemodel.Feature{
Name: basemodel.OSS,
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.SmartTraceDetail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.CustomMetricsFunction,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderPanels,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.QueryBuilderAlerts,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelSlack,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelWebhook,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelPagerduty,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelOpsgenie,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelEmail,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{
Name: basemodel.AlertChannelMsTeams,
Active: true,
Usage: 0,
UsageLimit: -1,
Route: "",
},
basemodel.Feature{ basemodel.Feature{
Name: basemodel.UseSpanMetrics, Name: basemodel.UseSpanMetrics,
Active: false, Active: false,
@@ -409,8 +122,8 @@ var EnterprisePlan = basemodel.FeatureSet{
Route: "", Route: "",
}, },
basemodel.Feature{ basemodel.Feature{
Name: basemodel.HostsInfraMonitoring, Name: basemodel.TraceFunnels,
Active: constants.EnableHostsInfraMonitoring(), Active: false,
Usage: 0, Usage: 0,
UsageLimit: -1, UsageLimit: -1,
Route: "", Route: "",

View File

@@ -53,7 +53,6 @@ type AnomalyRule struct {
func NewAnomalyRule( func NewAnomalyRule(
id string, id string,
p *baserules.PostableRule, p *baserules.PostableRule,
featureFlags interfaces.FeatureLookup,
reader interfaces.Reader, reader interfaces.Reader,
cache cache.Cache, cache cache.Cache,
opts ...baserules.RuleOption, opts ...baserules.RuleOption,
@@ -89,10 +88,9 @@ func NewAnomalyRule(
zap.L().Info("using seasonality", zap.String("seasonality", t.seasonality.String())) zap.L().Info("using seasonality", zap.String("seasonality", t.seasonality.String()))
querierOptsV2 := querierV2.QuerierOptions{ querierOptsV2 := querierV2.QuerierOptions{
Reader: reader, Reader: reader,
Cache: cache, Cache: cache,
KeyGenerator: queryBuilder.NewKeyGenerator(), KeyGenerator: queryBuilder.NewKeyGenerator(),
FeatureLookup: featureFlags,
} }
t.querierV2 = querierV2.NewQuerier(querierOptsV2) t.querierV2 = querierV2.NewQuerier(querierOptsV2)
@@ -102,21 +100,18 @@ func NewAnomalyRule(
anomaly.WithCache[*anomaly.HourlyProvider](cache), anomaly.WithCache[*anomaly.HourlyProvider](cache),
anomaly.WithKeyGenerator[*anomaly.HourlyProvider](queryBuilder.NewKeyGenerator()), anomaly.WithKeyGenerator[*anomaly.HourlyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.HourlyProvider](reader), anomaly.WithReader[*anomaly.HourlyProvider](reader),
anomaly.WithFeatureLookup[*anomaly.HourlyProvider](featureFlags),
) )
} else if t.seasonality == anomaly.SeasonalityDaily { } else if t.seasonality == anomaly.SeasonalityDaily {
t.provider = anomaly.NewDailyProvider( t.provider = anomaly.NewDailyProvider(
anomaly.WithCache[*anomaly.DailyProvider](cache), anomaly.WithCache[*anomaly.DailyProvider](cache),
anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()), anomaly.WithKeyGenerator[*anomaly.DailyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.DailyProvider](reader), anomaly.WithReader[*anomaly.DailyProvider](reader),
anomaly.WithFeatureLookup[*anomaly.DailyProvider](featureFlags),
) )
} else if t.seasonality == anomaly.SeasonalityWeekly { } else if t.seasonality == anomaly.SeasonalityWeekly {
t.provider = anomaly.NewWeeklyProvider( t.provider = anomaly.NewWeeklyProvider(
anomaly.WithCache[*anomaly.WeeklyProvider](cache), anomaly.WithCache[*anomaly.WeeklyProvider](cache),
anomaly.WithKeyGenerator[*anomaly.WeeklyProvider](queryBuilder.NewKeyGenerator()), anomaly.WithKeyGenerator[*anomaly.WeeklyProvider](queryBuilder.NewKeyGenerator()),
anomaly.WithReader[*anomaly.WeeklyProvider](reader), anomaly.WithReader[*anomaly.WeeklyProvider](reader),
anomaly.WithFeatureLookup[*anomaly.WeeklyProvider](featureFlags),
) )
} }
return &t, nil return &t, nil

View File

@@ -23,7 +23,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
tr, err := baserules.NewThresholdRule( tr, err := baserules.NewThresholdRule(
ruleId, ruleId,
opts.Rule, opts.Rule,
opts.FF,
opts.Reader, opts.Reader,
opts.UseLogsNewSchema, opts.UseLogsNewSchema,
opts.UseTraceNewSchema, opts.UseTraceNewSchema,
@@ -48,7 +47,7 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
opts.Rule, opts.Rule,
opts.Logger, opts.Logger,
opts.Reader, opts.Reader,
opts.ManagerOpts.PqlEngine, opts.ManagerOpts.Prometheus,
baserules.WithSQLStore(opts.SQLStore), baserules.WithSQLStore(opts.SQLStore),
) )
@@ -66,7 +65,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
ar, err := NewAnomalyRule( ar, err := NewAnomalyRule(
ruleId, ruleId,
opts.Rule, opts.Rule,
opts.FF,
opts.Reader, opts.Reader,
opts.Cache, opts.Cache,
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay), baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
@@ -123,7 +121,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
rule, err = baserules.NewThresholdRule( rule, err = baserules.NewThresholdRule(
alertname, alertname,
parsedRule, parsedRule,
opts.FF,
opts.Reader, opts.Reader,
opts.UseLogsNewSchema, opts.UseLogsNewSchema,
opts.UseTraceNewSchema, opts.UseTraceNewSchema,
@@ -145,7 +142,7 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
parsedRule, parsedRule,
opts.Logger, opts.Logger,
opts.Reader, opts.Reader,
opts.ManagerOpts.PqlEngine, opts.ManagerOpts.Prometheus,
baserules.WithSendAlways(), baserules.WithSendAlways(),
baserules.WithSendUnmatched(), baserules.WithSendUnmatched(),
baserules.WithSQLStore(opts.SQLStore), baserules.WithSQLStore(opts.SQLStore),
@@ -160,7 +157,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
rule, err = NewAnomalyRule( rule, err = NewAnomalyRule(
alertname, alertname,
parsedRule, parsedRule,
opts.FF,
opts.Reader, opts.Reader,
opts.Cache, opts.Cache,
baserules.WithSendAlways(), baserules.WithSendAlways(),

View File

@@ -2,11 +2,30 @@ package postgressqlstore
import ( import (
"context" "context"
"fmt"
"reflect" "reflect"
"slices"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/uptrace/bun" "github.com/uptrace/bun"
) )
var (
Identity = "id"
Integer = "bigint"
Text = "text"
)
var (
Org = "org"
User = "user"
)
var (
OrgReference = `("org_id") REFERENCES "organizations" ("id")`
UserReference = `("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE`
)
type dialect struct { type dialect struct {
} }
@@ -174,7 +193,10 @@ func (dialect *dialect) TableExists(ctx context.Context, bun bun.IDB, table inte
return true, nil return true, nil
} }
func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, cb func(context.Context) error) error { func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, references []string, cb func(context.Context) error) error {
if len(references) == 0 {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot run migration without reference")
}
exists, err := dialect.TableExists(ctx, bun, newModel) exists, err := dialect.TableExists(ctx, bun, newModel)
if err != nil { if err != nil {
return err return err
@@ -183,12 +205,25 @@ func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.I
return nil return nil
} }
_, err = bun. var fkReferences []string
for _, reference := range references {
if reference == Org && !slices.Contains(fkReferences, OrgReference) {
fkReferences = append(fkReferences, OrgReference)
} else if reference == User && !slices.Contains(fkReferences, UserReference) {
fkReferences = append(fkReferences, UserReference)
}
}
createTable := bun.
NewCreateTable(). NewCreateTable().
IfNotExists(). IfNotExists().
Model(newModel). Model(newModel)
Exec(ctx)
for _, fk := range fkReferences {
createTable = createTable.ForeignKey(fk)
}
_, err = createTable.Exec(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -209,3 +244,123 @@ func (dialect *dialect) RenameTableAndModifyModel(ctx context.Context, bun bun.I
return nil return nil
} }
func (dialect *dialect) AddNotNullDefaultToColumn(ctx context.Context, bun bun.IDB, table string, column, columnType, defaultValue string) error {
query := fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s SET DEFAULT %s, ALTER COLUMN %s SET NOT NULL", table, column, defaultValue, column)
if _, err := bun.ExecContext(ctx, query); err != nil {
return err
}
return nil
}
func (dialect *dialect) UpdatePrimaryKey(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, reference string, cb func(context.Context) error) error {
if reference == "" {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot run migration without reference")
}
oldTableName := bun.Dialect().Tables().Get(reflect.TypeOf(oldModel)).Name
newTableName := bun.Dialect().Tables().Get(reflect.TypeOf(newModel)).Name
columnType, err := dialect.GetColumnType(ctx, bun, oldTableName, Identity)
if err != nil {
return err
}
if columnType == Text {
return nil
}
fkReference := ""
if reference == Org {
fkReference = OrgReference
} else if reference == User {
fkReference = UserReference
}
_, err = bun.
NewCreateTable().
IfNotExists().
Model(newModel).
ForeignKey(fkReference).
Exec(ctx)
if err != nil {
return err
}
err = cb(ctx)
if err != nil {
return err
}
_, err = bun.
NewDropTable().
IfExists().
Model(oldModel).
Exec(ctx)
if err != nil {
return err
}
_, err = bun.
ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s RENAME TO %s", newTableName, oldTableName))
if err != nil {
return err
}
return nil
}
func (dialect *dialect) AddPrimaryKey(ctx context.Context, bun bun.IDB, oldModel interface{}, newModel interface{}, reference string, cb func(context.Context) error) error {
if reference == "" {
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "cannot run migration without reference")
}
oldTableName := bun.Dialect().Tables().Get(reflect.TypeOf(oldModel)).Name
newTableName := bun.Dialect().Tables().Get(reflect.TypeOf(newModel)).Name
identityExists, err := dialect.ColumnExists(ctx, bun, oldTableName, Identity)
if err != nil {
return err
}
if identityExists {
return nil
}
fkReference := ""
if reference == Org {
fkReference = OrgReference
} else if reference == User {
fkReference = UserReference
}
_, err = bun.
NewCreateTable().
IfNotExists().
Model(newModel).
ForeignKey(fkReference).
Exec(ctx)
if err != nil {
return err
}
err = cb(ctx)
if err != nil {
return err
}
_, err = bun.
NewDropTable().
IfExists().
Model(oldModel).
Exec(ctx)
if err != nil {
return err
}
_, err = bun.
ExecContext(ctx, fmt.Sprintf("ALTER TABLE %s RENAME TO %s", newTableName, oldTableName))
if err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,76 @@
package types
import (
"crypto/rand"
"encoding/base64"
"time"
"github.com/SigNoz/signoz/pkg/types"
"github.com/SigNoz/signoz/pkg/valuer"
"github.com/uptrace/bun"
)
type GettablePAT struct {
CreatedByUser PatUser `json:"createdByUser"`
UpdatedByUser PatUser `json:"updatedByUser"`
StorablePersonalAccessToken
}
type PatUser struct {
types.User
NotFound bool `json:"notFound"`
}
func NewGettablePAT(name, role, userID string, expiresAt int64) GettablePAT {
return GettablePAT{
StorablePersonalAccessToken: NewStorablePersonalAccessToken(name, role, userID, expiresAt),
}
}
type StorablePersonalAccessToken struct {
bun.BaseModel `bun:"table:personal_access_token"`
types.Identifiable
types.TimeAuditable
OrgID string `json:"orgId" bun:"org_id,type:text,notnull"`
Role string `json:"role" bun:"role,type:text,notnull,default:'ADMIN'"`
UserID string `json:"userId" bun:"user_id,type:text,notnull"`
Token string `json:"token" bun:"token,type:text,notnull,unique"`
Name string `json:"name" bun:"name,type:text,notnull"`
ExpiresAt int64 `json:"expiresAt" bun:"expires_at,notnull,default:0"`
LastUsed int64 `json:"lastUsed" bun:"last_used,notnull,default:0"`
Revoked bool `json:"revoked" bun:"revoked,notnull,default:false"`
UpdatedByUserID string `json:"updatedByUserId" bun:"updated_by_user_id,type:text,notnull,default:''"`
}
func NewStorablePersonalAccessToken(name, role, userID string, expiresAt int64) StorablePersonalAccessToken {
now := time.Now()
if expiresAt != 0 {
// convert expiresAt to unix timestamp from days
expiresAt = now.Unix() + (expiresAt * 24 * 60 * 60)
}
// Generate a 32-byte random token.
token := make([]byte, 32)
rand.Read(token)
// Encode the token in base64.
encodedToken := base64.StdEncoding.EncodeToString(token)
return StorablePersonalAccessToken{
Token: encodedToken,
Name: name,
Role: role,
UserID: userID,
ExpiresAt: expiresAt,
LastUsed: 0,
Revoked: false,
UpdatedByUserID: "",
TimeAuditable: types.TimeAuditable{
CreatedAt: now,
UpdatedAt: now,
},
Identifiable: types.Identifiable{
ID: valuer.GenerateUUID(),
},
}
}

View File

@@ -18,6 +18,13 @@
"field_send_resolved": "Send resolved alerts", "field_send_resolved": "Send resolved alerts",
"field_channel_type": "Type", "field_channel_type": "Type",
"field_webhook_url": "Webhook URL", "field_webhook_url": "Webhook URL",
"tooltip_webhook_url": "The URL of the webhook to send alerts to. Learn more about webhook integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/webhook/). Integrates with [Incident.io](https://signoz.io/docs/alerts-management/notification-channel/incident-io/), [Rootly](https://signoz.io/docs/alerts-management/notification-channel/rootly/), [Zenduty](https://signoz.io/docs/alerts-management/notification-channel/zenduty/) and [more](https://signoz.io/docs/alerts-management/notification-channel/webhook/#my-incident-management-tool-is-not-listed-can-i-still-integrate).",
"tooltip_slack_url": "The URL of the slack [incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) to send alerts to. Learn more about slack integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/slack/).",
"tooltip_pager_routing_key": "Learn how to obtain the routing key from your PagerDuty account [here](https://signoz.io/docs/alerts-management/notification-channel/pagerduty/#obtaining-integration-or-routing-key).",
"tooltip_opsgenie_api_key": "Learn how to obtain the API key from your OpsGenie account [here](https://support.atlassian.com/opsgenie/docs/integrate-opsgenie-with-prometheus/).",
"tooltip_email_to": "Enter email addresses separated by commas.",
"tooltip_ms_teams_url": "The URL of the Microsoft Teams [webhook](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498) to send alerts to. Learn more about Microsoft Teams integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/ms-teams/).",
"field_slack_recipient": "Recipient", "field_slack_recipient": "Recipient",
"field_slack_title": "Title", "field_slack_title": "Title",
"field_slack_description": "Description", "field_slack_description": "Description",

View File

@@ -18,6 +18,12 @@
"field_send_resolved": "Send resolved alerts", "field_send_resolved": "Send resolved alerts",
"field_channel_type": "Type", "field_channel_type": "Type",
"field_webhook_url": "Webhook URL", "field_webhook_url": "Webhook URL",
"tooltip_webhook_url": "The URL of the webhook to send alerts to. Learn more about webhook integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/webhook/). Integrates with [Incident.io](https://signoz.io/docs/alerts-management/notification-channel/incident-io/), [Rootly](https://signoz.io/docs/alerts-management/notification-channel/rootly/), [Zenduty](https://signoz.io/docs/alerts-management/notification-channel/zenduty/) and [more](https://signoz.io/docs/alerts-management/notification-channel/webhook/#my-incident-management-tool-is-not-listed-can-i-still-integrate).",
"tooltip_slack_url": "The URL of the slack [incoming webhook](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/) to send alerts to. Learn more about slack integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/slack/).",
"tooltip_pager_routing_key": "Learn how to obtain the routing key from your PagerDuty account [here](https://signoz.io/docs/alerts-management/notification-channel/pagerduty/#obtaining-integration-or-routing-key).",
"tooltip_opsgenie_api_key": "Learn how to obtain the API key from your OpsGenie account [here](https://support.atlassian.com/opsgenie/docs/integrate-opsgenie-with-prometheus/).",
"tooltip_email_to": "Enter email addresses separated by commas.",
"tooltip_ms_teams_url": "The URL of the Microsoft Teams [webhook](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498) to send alerts to. Learn more about Microsoft Teams integration in the docs [here](https://signoz.io/docs/alerts-management/notification-channel/ms-teams/).",
"field_slack_recipient": "Recipient", "field_slack_recipient": "Recipient",
"field_slack_title": "Title", "field_slack_title": "Title",
"field_slack_description": "Description", "field_slack_description": "Description",

View File

@@ -60,10 +60,14 @@
"INTEGRATIONS": "SigNoz | Integrations", "INTEGRATIONS": "SigNoz | Integrations",
"ALERT_HISTORY": "SigNoz | Alert Rule History", "ALERT_HISTORY": "SigNoz | Alert Rule History",
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview", "ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
"MESSAGING_QUEUES": "SigNoz | Messaging Queues", "MESSAGING_QUEUES_OVERVIEW": "SigNoz | Messaging Queues",
"MESSAGING_QUEUES_KAFKA": "SigNoz | Messaging Queues | Kafka",
"MESSAGING_QUEUES_KAFKA_DETAIL": "SigNoz | Messaging Queues | Kafka",
"MESSAGING_QUEUES_CELERY_TASK": "SigNoz | Messaging Queues | Celery",
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring", "INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring",
"INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring", "INFRASTRUCTURE_MONITORING_KUBERNETES": "SigNoz | Infra Monitoring",
"METRICS_EXPLORER": "SigNoz | Metrics Explorer", "METRICS_EXPLORER": "SigNoz | Metrics Explorer",
"METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer", "METRICS_EXPLORER_EXPLORER": "SigNoz | Metrics Explorer",
"METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer" "METRICS_EXPLORER_VIEWS": "SigNoz | Metrics Explorer",
"API_MONITORING": "SigNoz | API Monitoring"
} }

View File

@@ -1,3 +1,4 @@
import * as Sentry from '@sentry/react';
import { ConfigProvider } from 'antd'; import { ConfigProvider } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get'; import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set'; import setLocalStorageApi from 'api/browser/localstorage/set';
@@ -15,6 +16,7 @@ import { LICENSE_PLAN_KEY } from 'hooks/useLicense';
import { NotificationProvider } from 'hooks/useNotifications'; import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute'; import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history'; import history from 'lib/history';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import posthog from 'posthog-js'; import posthog from 'posthog-js';
import AlertRuleProvider from 'providers/Alert'; import AlertRuleProvider from 'providers/Alert';
import { useAppContext } from 'providers/App/App'; import { useAppContext } from 'providers/App/App';
@@ -26,6 +28,7 @@ import { Route, Router, Switch } from 'react-router-dom';
import { CompatRouter } from 'react-router-dom-v5-compat'; import { CompatRouter } from 'react-router-dom-v5-compat';
import { extractDomain } from 'utils/app'; import { extractDomain } from 'utils/app';
import { Home } from './pageComponents';
import PrivateRoute from './Private'; import PrivateRoute from './Private';
import defaultRoutes, { import defaultRoutes, {
AppRoutes, AppRoutes,
@@ -45,7 +48,6 @@ function App(): JSX.Element {
activeLicenseV3, activeLicenseV3,
isFetchingActiveLicenseV3, isFetchingActiveLicenseV3,
userFetchError, userFetchError,
licensesFetchError,
featureFlagsFetchError, featureFlagsFetchError,
isLoggedIn: isLoggedInState, isLoggedIn: isLoggedInState,
featureFlags, featureFlags,
@@ -55,10 +57,7 @@ function App(): JSX.Element {
const { hostname, pathname } = window.location; const { hostname, pathname } = window.location;
const { const { isCloudUser, isEnterpriseSelfHostedUser } = useGetTenantLicense();
isCloudUser: isCloudUserVal,
isEECloudUser: isEECloudUserVal,
} = useGetTenantLicense();
const enableAnalytics = useCallback( const enableAnalytics = useCallback(
(user: IUser): void => { (user: IUser): void => {
@@ -168,7 +167,7 @@ function App(): JSX.Element {
let updatedRoutes = defaultRoutes; let updatedRoutes = defaultRoutes;
// if the user is a cloud user // if the user is a cloud user
if (isCloudUserVal || isEECloudUserVal) { if (isCloudUser || isEnterpriseSelfHostedUser) {
// if the user is on basic plan then remove billing // if the user is on basic plan then remove billing
if (isOnBasicPlan) { if (isOnBasicPlan) {
updatedRoutes = updatedRoutes.filter( updatedRoutes = updatedRoutes.filter(
@@ -190,10 +189,10 @@ function App(): JSX.Element {
isLoggedInState, isLoggedInState,
user, user,
licenses, licenses,
isCloudUserVal, isCloudUser,
isEnterpriseSelfHostedUser,
isFetchingLicenses, isFetchingLicenses,
isFetchingUser, isFetchingUser,
isEECloudUserVal,
]); ]);
useEffect(() => { useEffect(() => {
@@ -208,6 +207,7 @@ function App(): JSX.Element {
} }
}, [pathname]); }, [pathname]);
// eslint-disable-next-line sonarjs/cognitive-complexity
useEffect(() => { useEffect(() => {
// feature flag shouldn't be loading and featureFlags or fetchError any one of this should be true indicating that req is complete // feature flag shouldn't be loading and featureFlags or fetchError any one of this should be true indicating that req is complete
// licenses should also be present. there is no check for licenses for loading and error as that is mandatory if not present then routing // licenses should also be present. there is no check for licenses for loading and error as that is mandatory if not present then routing
@@ -233,7 +233,12 @@ function App(): JSX.Element {
const showAddCreditCardModal = const showAddCreditCardModal =
!isPremiumSupportEnabled && !trialInfo?.trialConvertedToSubscription; !isPremiumSupportEnabled && !trialInfo?.trialConvertedToSubscription;
if (isLoggedInState && isChatSupportEnabled && !showAddCreditCardModal) { if (
isLoggedInState &&
isChatSupportEnabled &&
!showAddCreditCardModal &&
(isCloudUser || isEnterpriseSelfHostedUser)
) {
window.Intercom('boot', { window.Intercom('boot', {
app_id: process.env.INTERCOM_APP_ID, app_id: process.env.INTERCOM_APP_ID,
email: user?.email || '', email: user?.email || '',
@@ -252,13 +257,53 @@ function App(): JSX.Element {
licenses, licenses,
activeLicenseV3, activeLicenseV3,
trialInfo, trialInfo,
isCloudUser,
isEnterpriseSelfHostedUser,
]); ]);
useEffect(() => { useEffect(() => {
if (!isFetchingUser && isCloudUserVal && user && user.email) { if (!isFetchingUser && isCloudUser && user && user.email) {
enableAnalytics(user); enableAnalytics(user);
} }
}, [user, isFetchingUser, isCloudUserVal, enableAnalytics]); }, [user, isFetchingUser, isCloudUser, enableAnalytics]);
useEffect(() => {
if (isCloudUser || isEnterpriseSelfHostedUser) {
if (process.env.POSTHOG_KEY) {
posthog.init(process.env.POSTHOG_KEY, {
api_host: 'https://us.i.posthog.com',
person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
});
}
Sentry.init({
dsn: process.env.SENTRY_DSN,
tunnel: process.env.TUNNEL_URL,
environment: 'production',
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration({
maskAllText: false,
blockAllMedia: false,
}),
],
// Performance Monitoring
tracesSampleRate: 1.0, // Capture 100% of the transactions
// Set 'tracePropagationTargets' to control for which URLs distributed tracing should be enabled
tracePropagationTargets: [],
// Session Replay
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
});
} else {
posthog.reset();
Sentry.close();
if (window.cioanalytics && typeof window.cioanalytics.reset === 'function') {
window.cioanalytics.reset();
}
}
}, [isCloudUser, isEnterpriseSelfHostedUser]);
// if the user is in logged in state // if the user is in logged in state
if (isLoggedInState) { if (isLoggedInState) {
@@ -270,60 +315,55 @@ function App(): JSX.Element {
// if the required calls fails then return a something went wrong error // if the required calls fails then return a something went wrong error
// this needs to be on top of data missing error because if there is an error, data will never be loaded and it will // this needs to be on top of data missing error because if there is an error, data will never be loaded and it will
// move to indefinitive loading // move to indefinitive loading
if ( if (userFetchError && pathname !== ROUTES.SOMETHING_WENT_WRONG) {
(userFetchError || licensesFetchError) &&
pathname !== ROUTES.SOMETHING_WENT_WRONG
) {
history.replace(ROUTES.SOMETHING_WENT_WRONG); history.replace(ROUTES.SOMETHING_WENT_WRONG);
} }
// if all of the data is not set then return a spinner, this is required because there is some gap between loading states and data setting // if all of the data is not set then return a spinner, this is required because there is some gap between loading states and data setting
if ( if ((!licenses || !user.email || !featureFlags) && !userFetchError) {
(!licenses || !user.email || !featureFlags) &&
!userFetchError &&
!licensesFetchError
) {
return <Spinner tip="Loading..." />; return <Spinner tip="Loading..." />;
} }
} }
return ( return (
<ConfigProvider theme={themeConfig}> <Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<Router history={history}> <ConfigProvider theme={themeConfig}>
<CompatRouter> <Router history={history}>
<NotificationProvider> <CompatRouter>
<PrivateRoute> <NotificationProvider>
<ResourceProvider> <PrivateRoute>
<QueryBuilderProvider> <ResourceProvider>
<DashboardProvider> <QueryBuilderProvider>
<KeyboardHotkeysProvider> <DashboardProvider>
<AlertRuleProvider> <KeyboardHotkeysProvider>
<AppLayout> <AlertRuleProvider>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}> <AppLayout>
<Switch> <Suspense fallback={<Spinner size="large" tip="Loading..." />}>
{routes.map(({ path, component, exact }) => ( <Switch>
<Route {routes.map(({ path, component, exact }) => (
key={`${path}`} <Route
exact={exact} key={`${path}`}
path={path} exact={exact}
component={component} path={path}
/> component={component}
))} />
))}
<Route path="*" component={NotFound} /> <Route exact path="/" component={Home} />
</Switch> <Route path="*" component={NotFound} />
</Suspense> </Switch>
</AppLayout> </Suspense>
</AlertRuleProvider> </AppLayout>
</KeyboardHotkeysProvider> </AlertRuleProvider>
</DashboardProvider> </KeyboardHotkeysProvider>
</QueryBuilderProvider> </DashboardProvider>
</ResourceProvider> </QueryBuilderProvider>
</PrivateRoute> </ResourceProvider>
</NotificationProvider> </PrivateRoute>
</CompatRouter> </NotificationProvider>
</Router> </CompatRouter>
</ConfigProvider> </Router>
</ConfigProvider>
</Sentry.ErrorBoundary>
); );
} }

View File

@@ -11,9 +11,12 @@ const logEvent = async (
rateLimited?: boolean, rateLimited?: boolean,
): Promise<SuccessResponse<EventSuccessPayloadProps> | ErrorResponse> => { ): Promise<SuccessResponse<EventSuccessPayloadProps> | ErrorResponse> => {
try { try {
// add tenant_url to attributes
const { hostname } = window.location;
const updatedAttributes = { ...attributes, tenant_url: hostname };
const response = await axios.post('/event', { const response = await axios.post('/event', {
eventName, eventName,
attributes, attributes: updatedAttributes,
eventType: eventType || 'track', eventType: eventType || 'track',
rateLimited: rateLimited || false, // TODO: Update this once we have a proper way to handle rate limiting rateLimited: rateLimited || false, // TODO: Update this once we have a proper way to handle rate limiting
}); });

View File

@@ -521,7 +521,7 @@ export default function CeleryOverviewTable({
locale={{ locale={{
emptyText: isLoading ? null : <Typography.Text>No data</Typography.Text>, emptyText: isLoading ? null : <Typography.Text>No data</Typography.Text>,
}} }}
scroll={{ x: true }} scroll={{ x: 'max-content' }}
showSorterTooltip showSorterTooltip
onDragColumn={handleDragColumn} onDragColumn={handleDragColumn}
onRow={(record): { onClick: () => void; className: string } => ({ onRow={(record): { onClick: () => void; className: string } => ({

View File

@@ -18,6 +18,7 @@ function CopyClipboardHOC({
notifications.success({ notifications.success({
message: notificationMessage, message: notificationMessage,
key: notificationMessage,
}); });
} }
}, [value, notifications, entityKey]); }, [value, notifications, entityKey]);

View File

@@ -1,3 +1,5 @@
import './ResizeTable.styles.scss';
import { SyntheticEvent, useMemo } from 'react'; import { SyntheticEvent, useMemo } from 'react';
import { Resizable, ResizeCallbackData } from 'react-resizable'; import { Resizable, ResizeCallbackData } from 'react-resizable';
@@ -10,8 +12,8 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
const handle = useMemo( const handle = useMemo(
() => ( () => (
<SpanStyle <SpanStyle
className="react-resizable-handle"
onClick={(e): void => e.stopPropagation()} onClick={(e): void => e.stopPropagation()}
className="resize-handle"
/> />
), ),
[], [],
@@ -19,7 +21,7 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
if (!width) { if (!width) {
// eslint-disable-next-line react/jsx-props-no-spreading // eslint-disable-next-line react/jsx-props-no-spreading
return <th {...restProps} />; return <th {...restProps} className="resizable-header" />;
} }
return ( return (
@@ -29,9 +31,10 @@ function ResizableHeader(props: ResizableHeaderProps): JSX.Element {
handle={handle} handle={handle}
onResize={onResize} onResize={onResize}
draggableOpts={enableUserSelectHack} draggableOpts={enableUserSelectHack}
minConstraints={[150, 0]}
> >
{/* eslint-disable-next-line react/jsx-props-no-spreading */} {/* eslint-disable-next-line react/jsx-props-no-spreading */}
<th {...restProps} /> <th {...restProps} className="resizable-header" />
</Resizable> </Resizable>
); );
} }

View File

@@ -0,0 +1,53 @@
.resizable-header {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
position: relative;
.ant-table-column-title {
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
}
}
.resize-main-table {
.ant-table-body {
.ant-table-tbody {
.ant-table-row {
.ant-table-cell {
.ant-typography {
white-space: unset;
}
}
}
}
}
}
.logs-table,
.traces-table {
.resize-table {
.resize-handle {
position: absolute;
top: 0;
bottom: 0;
inset-inline-end: -5px;
width: 10px;
cursor: col-resize;
&::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 1px;
height: 1.6em;
background-color: var(--bg-slate-200);
transition: background-color 0.2s;
}
}
}
}

View File

@@ -2,35 +2,63 @@
import { Table } from 'antd'; import { Table } from 'antd';
import { ColumnsType } from 'antd/lib/table'; import { ColumnsType } from 'antd/lib/table';
import cx from 'classnames';
import { dragColumnParams } from 'hooks/useDragColumns/configs'; import { dragColumnParams } from 'hooks/useDragColumns/configs';
import { set } from 'lodash-es'; import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { debounce, set } from 'lodash-es';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { import {
SyntheticEvent, SyntheticEvent,
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useRef,
useState, useState,
} from 'react'; } from 'react';
import ReactDragListView from 'react-drag-listview'; import ReactDragListView from 'react-drag-listview';
import { ResizeCallbackData } from 'react-resizable'; import { ResizeCallbackData } from 'react-resizable';
import { Widgets } from 'types/api/dashboard/getAll';
import ResizableHeader from './ResizableHeader'; import ResizableHeader from './ResizableHeader';
import { DragSpanStyle } from './styles'; import { DragSpanStyle } from './styles';
import { ResizeTableProps } from './types'; import { ResizeTableProps } from './types';
// eslint-disable-next-line sonarjs/cognitive-complexity
function ResizeTable({ function ResizeTable({
columns, columns,
onDragColumn, onDragColumn,
pagination, pagination,
widgetId,
shouldPersistColumnWidths = false,
...restProps ...restProps
}: ResizeTableProps): JSX.Element { }: ResizeTableProps): JSX.Element {
const [columnsData, setColumns] = useState<ColumnsType>([]); const [columnsData, setColumns] = useState<ColumnsType>([]);
const { setColumnWidths, selectedDashboard } = useDashboard();
const columnWidths = shouldPersistColumnWidths
? (selectedDashboard?.data?.widgets?.find(
(widget) => widget.id === widgetId,
) as Widgets)?.columnWidths
: undefined;
const updateAllColumnWidths = useRef(
debounce((widthsConfig: Record<string, number>) => {
if (!widgetId || !shouldPersistColumnWidths) return;
setColumnWidths?.((prev) => ({
...prev,
[widgetId]: widthsConfig,
}));
}, 1000),
).current;
const handleResize = useCallback( const handleResize = useCallback(
(index: number) => ( (index: number) => (
_e: SyntheticEvent<Element>, e: SyntheticEvent<Element>,
{ size }: ResizeCallbackData, { size }: ResizeCallbackData,
): void => { ): void => {
e.preventDefault();
e.stopPropagation();
const newColumns = [...columnsData]; const newColumns = [...columnsData];
newColumns[index] = { newColumns[index] = {
...newColumns[index], ...newColumns[index],
@@ -65,6 +93,7 @@ function ResizeTable({
...restProps, ...restProps,
components: { header: { cell: ResizableHeader } }, components: { header: { cell: ResizableHeader } },
columns: mergedColumns, columns: mergedColumns,
className: cx('resize-main-table', restProps.className),
}; };
set( set(
@@ -78,9 +107,39 @@ function ResizeTable({
useEffect(() => { useEffect(() => {
if (columns) { if (columns) {
setColumns(columns); // Apply stored column widths from widget configuration
const columnsWithStoredWidths = columns.map((col) => {
const dataIndex = (col as RowData).dataIndex as string;
if (dataIndex && columnWidths && columnWidths[dataIndex]) {
return {
...col,
width: columnWidths[dataIndex], // Apply stored width
};
}
return col;
});
setColumns(columnsWithStoredWidths);
} }
}, [columns]); }, [columns, columnWidths]);
useEffect(() => {
if (!shouldPersistColumnWidths) return;
// Collect all column widths in a single object
const newColumnWidths: Record<string, number> = {};
mergedColumns.forEach((col) => {
if (col.width && (col as RowData).dataIndex) {
const dataIndex = (col as RowData).dataIndex as string;
newColumnWidths[dataIndex] = col.width as number;
}
});
// Only update if there are actual widths to set
if (Object.keys(newColumnWidths).length > 0) {
updateAllColumnWidths(newColumnWidths);
}
}, [mergedColumns, updateAllColumnWidths, shouldPersistColumnWidths]);
return onDragColumn ? ( return onDragColumn ? (
<ReactDragListView.DragColumn {...dragColumnParams} onDragEnd={onDragColumn}> <ReactDragListView.DragColumn {...dragColumnParams} onDragEnd={onDragColumn}>

View File

@@ -8,6 +8,8 @@ export const SpanStyle = styled.span`
width: 0.625rem; width: 0.625rem;
height: 100%; height: 100%;
cursor: col-resize; cursor: col-resize;
margin-left: 4px;
margin-right: 4px;
`; `;
export const DragSpanStyle = styled.span` export const DragSpanStyle = styled.span`

View File

@@ -9,6 +9,8 @@ import { TableDataSource } from './contants';
export interface ResizeTableProps extends TableProps<any> { export interface ResizeTableProps extends TableProps<any> {
onDragColumn?: (fromIndex: number, toIndex: number) => void; onDragColumn?: (fromIndex: number, toIndex: number) => void;
widgetId?: string;
shouldPersistColumnWidths?: boolean;
} }
export interface DynamicColumnTableProps extends TableProps<any> { export interface DynamicColumnTableProps extends TableProps<any> {
tablesource: typeof TableDataSource[keyof typeof TableDataSource]; tablesource: typeof TableDataSource[keyof typeof TableDataSource];

View File

@@ -1,28 +1,12 @@
// keep this consistent with backend constants.go // keep this consistent with backend constants.go
export enum FeatureKeys { export enum FeatureKeys {
SSO = 'SSO', SSO = 'SSO',
ENTERPRISE_PLAN = 'ENTERPRISE_PLAN',
BASIC_PLAN = 'BASIC_PLAN',
ALERT_CHANNEL_SLACK = 'ALERT_CHANNEL_SLACK',
ALERT_CHANNEL_WEBHOOK = 'ALERT_CHANNEL_WEBHOOK',
ALERT_CHANNEL_PAGERDUTY = 'ALERT_CHANNEL_PAGERDUTY',
ALERT_CHANNEL_OPSGENIE = 'ALERT_CHANNEL_OPSGENIE',
ALERT_CHANNEL_MSTEAMS = 'ALERT_CHANNEL_MSTEAMS',
DurationSort = 'DurationSort',
TimestampSort = 'TimestampSort',
SMART_TRACE_DETAIL = 'SMART_TRACE_DETAIL',
CUSTOM_METRICS_FUNCTION = 'CUSTOM_METRICS_FUNCTION',
QUERY_BUILDER_PANELS = 'QUERY_BUILDER_PANELS',
QUERY_BUILDER_ALERTS = 'QUERY_BUILDER_ALERTS',
DISABLE_UPSELL = 'DISABLE_UPSELL',
USE_SPAN_METRICS = 'USE_SPAN_METRICS', USE_SPAN_METRICS = 'USE_SPAN_METRICS',
OSS = 'OSS',
ONBOARDING = 'ONBOARDING', ONBOARDING = 'ONBOARDING',
CHAT_SUPPORT = 'CHAT_SUPPORT', CHAT_SUPPORT = 'CHAT_SUPPORT',
GATEWAY = 'GATEWAY', GATEWAY = 'GATEWAY',
PREMIUM_SUPPORT = 'PREMIUM_SUPPORT', PREMIUM_SUPPORT = 'PREMIUM_SUPPORT',
QUERY_BUILDER_SEARCH_V2 = 'QUERY_BUILDER_SEARCH_V2',
ANOMALY_DETECTION = 'ANOMALY_DETECTION', ANOMALY_DETECTION = 'ANOMALY_DETECTION',
AWS_INTEGRATION = 'AWS_INTEGRATION',
ONBOARDING_V3 = 'ONBOARDING_V3', ONBOARDING_V3 = 'ONBOARDING_V3',
TRACE_FUNNELS = 'TRACE_FUNNELS',
} }

View File

@@ -1,3 +1,4 @@
import ROUTES from 'constants/routes';
import AlertChannels from 'container/AllAlertChannels'; import AlertChannels from 'container/AllAlertChannels';
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts'; import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils'; import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
@@ -20,6 +21,13 @@ jest.mock('hooks/useNotifications', () => ({
})), })),
})); }));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string } => ({
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.ALL_CHANNELS}`,
}),
}));
describe('Alert Channels Settings List page', () => { describe('Alert Channels Settings List page', () => {
beforeEach(() => { beforeEach(() => {
render(<AlertChannels />); render(<AlertChannels />);

View File

@@ -1,3 +1,4 @@
import ROUTES from 'constants/routes';
import AlertChannels from 'container/AllAlertChannels'; import AlertChannels from 'container/AllAlertChannels';
import { allAlertChannels } from 'mocks-server/__mockdata__/alerts'; import { allAlertChannels } from 'mocks-server/__mockdata__/alerts';
import { fireEvent, render, screen, waitFor } from 'tests/test-utils'; import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
@@ -25,6 +26,13 @@ jest.mock('hooks/useComponentPermission', () => ({
default: jest.fn().mockImplementation(() => [false]), default: jest.fn().mockImplementation(() => [false]),
})); }));
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useLocation: (): { pathname: string } => ({
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.ALL_CHANNELS}`,
}),
}));
describe('Alert Channels Settings List page (Normal User)', () => { describe('Alert Channels Settings List page (Normal User)', () => {
beforeEach(() => { beforeEach(() => {
render(<AlertChannels />); render(<AlertChannels />);

View File

@@ -31,6 +31,10 @@ jest.mock('hooks/useNotifications', () => ({
})), })),
})); }));
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
}));
describe('Create Alert Channel', () => { describe('Create Alert Channel', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();

View File

@@ -18,6 +18,10 @@ import { render, screen } from 'tests/test-utils';
import { testLabelInputAndHelpValue } from './testUtils'; import { testLabelInputAndHelpValue } from './testUtils';
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
}));
describe('Create Alert Channel (Normal User)', () => { describe('Create Alert Channel (Normal User)', () => {
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();

View File

@@ -20,6 +20,10 @@ jest.mock('hooks/useNotifications', () => ({
})), })),
})); }));
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
}));
describe('Should check if the edit alert channel is properly displayed ', () => { describe('Should check if the edit alert channel is properly displayed ', () => {
beforeEach(() => { beforeEach(() => {
render(<EditAlertChannels initialValue={editAlertChannelInitialValue} />); render(<EditAlertChannels initialValue={editAlertChannelInitialValue} />);

View File

@@ -1,5 +1,6 @@
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { Select, Spin, Table, Typography } from 'antd'; import { Select, Spin, Table, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app'; import { ENTITY_VERSION_V4 } from 'constants/app';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { import {
@@ -151,6 +152,7 @@ function AllEndPoints({
if (groupBy.length === 0) { if (groupBy.length === 0) {
setSelectedEndPointName(record.endpointName); // this will open up the endpoint details tab setSelectedEndPointName(record.endpointName); // this will open up the endpoint details tab
setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS); setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS);
logEvent('API Monitoring: Endpoint name row clicked', {});
} else { } else {
handleGroupByRowClick(record); // this will prepare the nested query payload handleGroupByRowClick(record); // this will prepare the nested query payload
} }

View File

@@ -392,6 +392,39 @@
gap: 20px; gap: 20px;
padding-top: 20px; padding-top: 20px;
.endpoint-meta-data {
display: flex;
gap: 8px;
.endpoint-meta-data-pill {
display: flex;
align-items: flex-start;
border-radius: 4px;
border: 1px solid var(--bg-slate-300);
width: fit-content;
.endpoint-meta-data-label {
display: flex;
padding: 6px 8px;
align-items: center;
gap: 4px;
border-right: 1px solid var(--bg-slate-300);
color: var(--text-vanilla-100);
background: var(--bg-slate-500);
height: calc(100% - 12px);
}
.endpoint-meta-data-value {
display: flex;
padding: 6px 8px;
justify-content: center;
align-items: center;
gap: 10px;
color: var(--text-vanilla-400);
background: var(--bg-slate-400);
height: calc(100% - 12px);
}
}
}
.endpoint-details-filters-container { .endpoint-details-filters-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -405,6 +438,13 @@
} }
} }
.ant-select-item,
.ant-select-item-option-content {
flex: auto;
white-space: normal;
overflow-wrap: break-word;
}
.status-code-table-container { .status-code-table-container {
border-radius: 3px; border-radius: 3px;
border: 1px solid var(--bg-slate-500); border: 1px solid var(--bg-slate-500);
@@ -809,6 +849,13 @@
width: 100%; width: 100%;
} }
} }
.ant-select-item,
.ant-select-item-option-content {
flex: auto;
white-space: normal;
overflow-wrap: break-word;
}
} }
.lightMode { .lightMode {
@@ -917,6 +964,20 @@
} }
} }
.endpoint-meta-data {
.endpoint-meta-data-pill {
.endpoint-meta-data-label {
color: var(--text-ink-300);
background: var(--bg-vanilla-100);
}
.endpoint-meta-data-value {
color: var(--text-ink-300);
background: var(--bg-vanilla-100);
}
}
}
.status-code-table-container { .status-code-table-container {
.ant-table { .ant-table {
.ant-table-thead > tr > th { .ant-table-thead > tr > th {

View File

@@ -19,12 +19,14 @@ function DomainDetails({
selectedDomainIndex, selectedDomainIndex,
setSelectedDomainIndex, setSelectedDomainIndex,
domainListLength, domainListLength,
domainListFilters,
}: { }: {
domainData: any; domainData: any;
handleClose: () => void; handleClose: () => void;
selectedDomainIndex: number; selectedDomainIndex: number;
setSelectedDomainIndex: (index: number) => void; setSelectedDomainIndex: (index: number) => void;
domainListLength: number; domainListLength: number;
domainListFilters: IBuilderQuery['filters'];
}): JSX.Element { }): JSX.Element {
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.ALL_ENDPOINTS); const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.ALL_ENDPOINTS);
const [selectedEndPointName, setSelectedEndPointName] = useState<string>(''); const [selectedEndPointName, setSelectedEndPointName] = useState<string>('');
@@ -132,6 +134,7 @@ function DomainDetails({
domainName={domainData.domainName} domainName={domainData.domainName}
endPointName={selectedEndPointName} endPointName={selectedEndPointName}
setSelectedEndPointName={setSelectedEndPointName} setSelectedEndPointName={setSelectedEndPointName}
domainListFilters={domainListFilters}
/> />
)} )}
</> </>

View File

@@ -2,7 +2,10 @@ import { ENTITY_VERSION_V4 } from 'constants/app';
import { initialQueriesMap } from 'constants/queryBuilder'; import { initialQueriesMap } from 'constants/queryBuilder';
import { import {
END_POINT_DETAILS_QUERY_KEYS_ARRAY, END_POINT_DETAILS_QUERY_KEYS_ARRAY,
extractPortAndEndpoint,
getEndPointDetailsQueryPayload, getEndPointDetailsQueryPayload,
getLatencyOverTimeWidgetData,
getRateOverTimeWidgetData,
} from 'container/ApiMonitoring/utils'; } from 'container/ApiMonitoring/utils';
import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2'; import QueryBuilderSearchV2 from 'container/QueryBuilder/filters/QueryBuilderSearchV2/QueryBuilderSearchV2';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults'; import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
@@ -27,10 +30,12 @@ function EndPointDetails({
domainName, domainName,
endPointName, endPointName,
setSelectedEndPointName, setSelectedEndPointName,
domainListFilters,
}: { }: {
domainName: string; domainName: string;
endPointName: string; endPointName: string;
setSelectedEndPointName: (value: string) => void; setSelectedEndPointName: (value: string) => void;
domainListFilters: IBuilderQuery['filters'];
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -101,8 +106,6 @@ function EndPointDetails({
const [ const [
endPointMetricsDataQuery, endPointMetricsDataQuery,
endPointStatusCodeDataQuery, endPointStatusCodeDataQuery,
endPointRateOverTimeDataQuery,
endPointLatencyOverTimeDataQuery,
endPointDropDownDataQuery, endPointDropDownDataQuery,
endPointDependentServicesDataQuery, endPointDependentServicesDataQuery,
endPointStatusCodeBarChartsDataQuery, endPointStatusCodeBarChartsDataQuery,
@@ -115,12 +118,29 @@ function EndPointDetails({
endPointDetailsDataQueries[3], endPointDetailsDataQueries[3],
endPointDetailsDataQueries[4], endPointDetailsDataQueries[4],
endPointDetailsDataQueries[5], endPointDetailsDataQueries[5],
endPointDetailsDataQueries[6],
endPointDetailsDataQueries[7],
], ],
[endPointDetailsDataQueries], [endPointDetailsDataQueries],
); );
const { endpoint, port } = useMemo(
() => extractPortAndEndpoint(endPointName),
[endPointName],
);
const [rateOverTimeWidget, latencyOverTimeWidget] = useMemo(
() => [
getRateOverTimeWidgetData(domainName, endPointName, {
items: [...domainListFilters.items, ...filters.items],
op: filters.op,
}),
getLatencyOverTimeWidgetData(domainName, endPointName, {
items: [...domainListFilters.items, ...filters.items],
op: filters.op,
}),
],
[domainName, endPointName, filters, domainListFilters],
);
return ( return (
<div className="endpoint-details-container"> <div className="endpoint-details-container">
<div className="endpoint-details-filters-container"> <div className="endpoint-details-filters-container">
@@ -129,6 +149,8 @@ function EndPointDetails({
selectedEndPointName={endPointName} selectedEndPointName={endPointName}
setSelectedEndPointName={setSelectedEndPointName} setSelectedEndPointName={setSelectedEndPointName}
endPointDropDownDataQuery={endPointDropDownDataQuery} endPointDropDownDataQuery={endPointDropDownDataQuery}
parentContainerDiv=".endpoint-details-filters-container"
dropdownStyle={{ width: 'calc(100% - 36px)' }}
/> />
</div> </div>
<div className="endpoint-details-filters-container-search"> <div className="endpoint-details-filters-container-search">
@@ -141,6 +163,16 @@ function EndPointDetails({
/> />
</div> </div>
</div> </div>
<div className="endpoint-meta-data">
<div className="endpoint-meta-data-pill">
<div className="endpoint-meta-data-label">Endpoint</div>
<div className="endpoint-meta-data-value">{endpoint || '-'}</div>
</div>
<div className="endpoint-meta-data-pill">
<div className="endpoint-meta-data-label">Port</div>
<div className="endpoint-meta-data-value">{port || '-'}</div>
</div>
</div>
<EndPointMetrics endPointMetricsDataQuery={endPointMetricsDataQuery} /> <EndPointMetrics endPointMetricsDataQuery={endPointMetricsDataQuery} />
{!isServicesFilterApplied && ( {!isServicesFilterApplied && (
<DependentServices <DependentServices
@@ -152,18 +184,14 @@ function EndPointDetails({
endPointStatusCodeLatencyBarChartsDataQuery={ endPointStatusCodeLatencyBarChartsDataQuery={
endPointStatusCodeLatencyBarChartsDataQuery endPointStatusCodeLatencyBarChartsDataQuery
} }
domainName={domainName}
endPointName={endPointName}
domainListFilters={domainListFilters}
filters={filters}
/> />
<StatusCodeTable endPointStatusCodeDataQuery={endPointStatusCodeDataQuery} /> <StatusCodeTable endPointStatusCodeDataQuery={endPointStatusCodeDataQuery} />
<MetricOverTimeGraph <MetricOverTimeGraph widget={rateOverTimeWidget} />
metricOverTimeDataQuery={endPointRateOverTimeDataQuery} <MetricOverTimeGraph widget={latencyOverTimeWidget} />
widgetInfoIndex={0}
endPointName={endPointName}
/>
<MetricOverTimeGraph
metricOverTimeDataQuery={endPointLatencyOverTimeDataQuery}
widgetInfoIndex={1}
endPointName={endPointName}
/>
</div> </div>
); );
} }

View File

@@ -8,6 +8,7 @@ import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import EndPointDetailsZeroState from './components/EndPointDetailsZeroState'; import EndPointDetailsZeroState from './components/EndPointDetailsZeroState';
@@ -17,10 +18,12 @@ function EndPointDetailsWrapper({
domainName, domainName,
endPointName, endPointName,
setSelectedEndPointName, setSelectedEndPointName,
domainListFilters,
}: { }: {
domainName: string; domainName: string;
endPointName: string; endPointName: string;
setSelectedEndPointName: (value: string) => void; setSelectedEndPointName: (value: string) => void;
domainListFilters: IBuilderQuery['filters'];
}): JSX.Element { }): JSX.Element {
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>( const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime, (state) => state.globalTime,
@@ -69,6 +72,7 @@ function EndPointDetailsWrapper({
domainName={domainName} domainName={domainName}
endPointName={endPointName} endPointName={endPointName}
setSelectedEndPointName={setSelectedEndPointName} setSelectedEndPointName={setSelectedEndPointName}
domainListFilters={domainListFilters}
/> />
); );
} }

View File

@@ -28,6 +28,8 @@ function EndPointDetailsZeroState({
<EndPointsDropDown <EndPointsDropDown
setSelectedEndPointName={setSelectedEndPointName} setSelectedEndPointName={setSelectedEndPointName}
endPointDropDownDataQuery={endPointDropDownDataQuery} endPointDropDownDataQuery={endPointDropDownDataQuery}
parentContainerDiv=".end-point-details-zero-state-wrapper"
dropdownStyle={{ width: '60%' }}
/> />
</div> </div>
</div> </div>

View File

@@ -70,7 +70,7 @@ function EndPointMetrics({
<Skeleton.Button active size="small" /> <Skeleton.Button active size="small" />
) : ( ) : (
<Tooltip title={metricsData?.rate}> <Tooltip title={metricsData?.rate}>
<span className="round-metric-tag">{metricsData?.rate}/sec</span> <span className="round-metric-tag">{metricsData?.rate} ops/sec</span>
</Tooltip> </Tooltip>
)} )}
</Typography.Text> </Typography.Text>

View File

@@ -8,16 +8,22 @@ interface EndPointsDropDownProps {
selectedEndPointName?: string; selectedEndPointName?: string;
setSelectedEndPointName: (value: string) => void; setSelectedEndPointName: (value: string) => void;
endPointDropDownDataQuery: UseQueryResult<SuccessResponse<any>, unknown>; endPointDropDownDataQuery: UseQueryResult<SuccessResponse<any>, unknown>;
parentContainerDiv?: string;
dropdownStyle?: React.CSSProperties;
} }
const defaultProps = { const defaultProps = {
selectedEndPointName: '', selectedEndPointName: '',
parentContainerDiv: '',
dropdownStyle: {},
}; };
function EndPointsDropDown({ function EndPointsDropDown({
selectedEndPointName, selectedEndPointName,
setSelectedEndPointName, setSelectedEndPointName,
endPointDropDownDataQuery, endPointDropDownDataQuery,
parentContainerDiv,
dropdownStyle,
}: EndPointsDropDownProps): JSX.Element { }: EndPointsDropDownProps): JSX.Element {
const { data, isLoading, isFetching } = endPointDropDownDataQuery; const { data, isLoading, isFetching } = endPointDropDownDataQuery;
@@ -39,6 +45,13 @@ function EndPointsDropDown({
style={{ width: '100%' }} style={{ width: '100%' }}
onChange={handleChange} onChange={handleChange}
options={formattedData} options={formattedData}
getPopupContainer={
parentContainerDiv
? (): HTMLElement =>
document.querySelector(parentContainerDiv) as HTMLElement
: (triggerNode): HTMLElement => triggerNode.parentNode as HTMLElement
}
dropdownStyle={dropdownStyle}
/> />
); );
} }

View File

@@ -1,6 +1,7 @@
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { Spin, Table } from 'antd'; import { Spin, Table } from 'antd';
import { ColumnType } from 'antd/lib/table'; import { ColumnType } from 'antd/lib/table';
import logEvent from 'api/common/logEvent';
import { ENTITY_VERSION_V4 } from 'constants/app'; import { ENTITY_VERSION_V4 } from 'constants/app';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { import {
@@ -114,6 +115,7 @@ function ExpandedRow({
onClick: (): void => { onClick: (): void => {
setSelectedEndPointName(record.endpointName); setSelectedEndPointName(record.endpointName);
setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS); setSelectedView(VIEW_TYPES.ENDPOINT_DETAILS);
logEvent('API Monitoring: Endpoint name row clicked', {});
}, },
className: 'expanded-clickable-row', className: 'expanded-clickable-row',
})} })}

View File

@@ -1,110 +1,18 @@
import { Card, Skeleton, Typography } from 'antd'; import { Card } from 'antd';
import cx from 'classnames'; import GridCard from 'container/GridCardLayout/GridCard';
import Uplot from 'components/Uplot'; import { Widgets } from 'types/api/dashboard/getAll';
import { PANEL_TYPES } from 'constants/queryBuilder';
import {
apiWidgetInfo,
extractPortAndEndpoint,
getFormattedChartData,
} from 'container/ApiMonitoring/utils';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useCallback, useMemo, useRef } from 'react';
import { UseQueryResult } from 'react-query';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api';
import { GlobalReducer } from 'types/reducer/globalTime';
import { Options } from 'uplot';
import ErrorState from './ErrorState';
function MetricOverTimeGraph({
metricOverTimeDataQuery,
widgetInfoIndex,
endPointName,
}: {
metricOverTimeDataQuery: UseQueryResult<SuccessResponse<any>, unknown>;
widgetInfoIndex: number;
endPointName: string;
}): JSX.Element {
const { data } = metricOverTimeDataQuery;
const { minTime, maxTime } = useSelector<AppState, GlobalReducer>(
(state) => state.globalTime,
);
const graphRef = useRef<HTMLDivElement>(null);
const dimensions = useResizeObserver(graphRef);
const { endpoint } = extractPortAndEndpoint(endPointName);
const formattedChartData = useMemo(
() => getFormattedChartData(data?.payload, [endpoint]),
[data?.payload, endpoint],
);
const chartData = useMemo(() => getUPlotChartData(formattedChartData), [
formattedChartData,
]);
const isDarkMode = useIsDarkMode();
const options = useMemo(
() =>
getUPlotChartOptions({
apiResponse: formattedChartData,
isDarkMode,
dimensions,
yAxisUnit: apiWidgetInfo[widgetInfoIndex].yAxisUnit,
softMax: null,
softMin: null,
minTimeScale: Math.floor(minTime / 1e9),
maxTimeScale: Math.floor(maxTime / 1e9),
panelType: PANEL_TYPES.TIME_SERIES,
}),
[
formattedChartData,
minTime,
maxTime,
widgetInfoIndex,
dimensions,
isDarkMode,
],
);
const renderCardContent = useCallback(
(query: UseQueryResult<SuccessResponse<any>, unknown>): JSX.Element => {
if (query.isLoading) {
return <Skeleton />;
}
if (query.error) {
return <ErrorState refetch={query.refetch} />;
}
return (
<div
className={cx('chart-container', {
'no-data-container':
!query.isLoading && !query?.data?.payload?.data?.result?.length,
})}
>
<Uplot options={options as Options} data={chartData} />
</div>
);
},
[options, chartData],
);
function MetricOverTimeGraph({ widget }: { widget: Widgets }): JSX.Element {
return ( return (
<div> <div>
<Card bordered className="endpoint-details-card"> <Card bordered className="endpoint-details-card">
<Typography.Text>{apiWidgetInfo[widgetInfoIndex].title}</Typography.Text> <div className="graph-container">
<div className="graph-container" ref={graphRef}> <GridCard
{renderCardContent(metricOverTimeDataQuery)} widget={widget}
isQueryEnabled
onDragSelect={(): void => {}}
customOnDragSelect={(): void => {}}
/>
</div> </div>
</Card> </Card>
</div> </div>

View File

@@ -1,13 +1,22 @@
import { Color } from '@signozhq/design-tokens';
import { Button, Card, Skeleton, Typography } from 'antd'; import { Button, Card, Skeleton, Typography } from 'antd';
import cx from 'classnames'; import cx from 'classnames';
import { useGetGraphCustomSeries } from 'components/CeleryTask/useGetGraphCustomSeries';
import { useNavigateToExplorer } from 'components/CeleryTask/useNavigateToExplorer';
import Uplot from 'components/Uplot'; import Uplot from 'components/Uplot';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { import {
getCustomFiltersForBarChart,
getFormattedEndPointStatusCodeChartData, getFormattedEndPointStatusCodeChartData,
getStatusCodeBarChartWidgetData,
statusCodeWidgetInfo, statusCodeWidgetInfo,
} from 'container/ApiMonitoring/utils'; } from 'container/ApiMonitoring/utils';
import { handleGraphClick } from 'container/GridCardLayout/GridCard/utils';
import { useGraphClickToShowButton } from 'container/GridCardLayout/useGraphClickToShowButton';
import useNavigateToExplorerPages from 'container/GridCardLayout/useNavigateToExplorerPages';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions'; import { useResizeObserver } from 'hooks/useDimensions';
import { useNotifications } from 'hooks/useNotifications';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { useCallback, useMemo, useRef, useState } from 'react'; import { useCallback, useMemo, useRef, useState } from 'react';
@@ -15,6 +24,8 @@ import { UseQueryResult } from 'react-query';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers'; import { AppState } from 'store/reducers';
import { SuccessResponse } from 'types/api'; import { SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { GlobalReducer } from 'types/reducer/globalTime'; import { GlobalReducer } from 'types/reducer/globalTime';
import { Options } from 'uplot'; import { Options } from 'uplot';
@@ -23,6 +34,10 @@ import ErrorState from './ErrorState';
function StatusCodeBarCharts({ function StatusCodeBarCharts({
endPointStatusCodeBarChartsDataQuery, endPointStatusCodeBarChartsDataQuery,
endPointStatusCodeLatencyBarChartsDataQuery, endPointStatusCodeLatencyBarChartsDataQuery,
domainName,
endPointName,
domainListFilters,
filters,
}: { }: {
endPointStatusCodeBarChartsDataQuery: UseQueryResult< endPointStatusCodeBarChartsDataQuery: UseQueryResult<
SuccessResponse<any>, SuccessResponse<any>,
@@ -32,6 +47,10 @@ function StatusCodeBarCharts({
SuccessResponse<any>, SuccessResponse<any>,
unknown unknown
>; >;
domainName: string;
endPointName: string;
domainListFilters: IBuilderQuery['filters'];
filters: IBuilderQuery['filters'];
}): JSX.Element { }): JSX.Element {
// 0 : Status Code Count // 0 : Status Code Count
// 1 : Status Code Latency // 1 : Status Code Latency
@@ -85,6 +104,72 @@ function StatusCodeBarCharts({
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const graphClick = useGraphClickToShowButton({
graphRef,
isButtonEnabled: true,
buttonClassName: 'view-onclick-show-button',
});
const navigateToExplorer = useNavigateToExplorer();
const navigateToExplorerPages = useNavigateToExplorerPages();
const { notifications } = useNotifications();
const { getCustomSeries } = useGetGraphCustomSeries({
isDarkMode,
drawStyle: 'bars',
colorMapping: {
'200-299': Color.BG_FOREST_500,
'300-399': Color.BG_AMBER_400,
'400-499': Color.BG_CHERRY_500,
'500-599': Color.BG_ROBIN_500,
Other: Color.BG_SIENNA_500,
},
});
const widget = useMemo<Widgets>(
() =>
getStatusCodeBarChartWidgetData(domainName, endPointName, {
items: [...domainListFilters.items, ...filters.items],
op: filters.op,
}),
[domainName, endPointName, domainListFilters, filters],
);
const graphClickHandler = useCallback(
(
xValue: number,
yValue: number,
mouseX: number,
mouseY: number,
metric?: { [key: string]: string },
queryData?: { queryName: string; inFocusOrNot: boolean },
): void => {
const customFilters = getCustomFiltersForBarChart(metric);
handleGraphClick({
xValue,
yValue,
mouseX,
mouseY,
metric,
queryData,
widget,
navigateToExplorerPages,
navigateToExplorer,
notifications,
graphClick,
customFilters,
});
},
[
widget,
navigateToExplorerPages,
navigateToExplorer,
notifications,
graphClick,
],
);
const options = useMemo( const options = useMemo(
() => () =>
getUPlotChartOptions({ getUPlotChartOptions({
@@ -100,6 +185,8 @@ function StatusCodeBarCharts({
minTimeScale: Math.floor(minTime / 1e9), minTimeScale: Math.floor(minTime / 1e9),
maxTimeScale: Math.floor(maxTime / 1e9), maxTimeScale: Math.floor(maxTime / 1e9),
panelType: PANEL_TYPES.BAR, panelType: PANEL_TYPES.BAR,
onClickHandler: graphClickHandler,
customSeries: getCustomSeries,
}), }),
[ [
minTime, minTime,
@@ -109,6 +196,8 @@ function StatusCodeBarCharts({
formattedEndPointStatusCodeBarChartsDataPayload, formattedEndPointStatusCodeBarChartsDataPayload,
formattedEndPointStatusCodeLatencyBarChartsDataPayload, formattedEndPointStatusCodeLatencyBarChartsDataPayload,
isDarkMode, isDarkMode,
graphClickHandler,
getCustomSeries,
], ],
); );

View File

@@ -3,6 +3,7 @@ import '../Explorer.styles.scss';
import { LoadingOutlined } from '@ant-design/icons'; import { LoadingOutlined } from '@ant-design/icons';
import { Spin, Table, Typography } from 'antd'; import { Spin, Table, Typography } from 'antd';
import axios from 'api'; import axios from 'api';
import logEvent from 'api/common/logEvent';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler'; import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import cx from 'classnames'; import cx from 'classnames';
@@ -130,6 +131,7 @@ function DomainList({
(item) => item.key === record.key, (item) => item.key === record.key,
); );
setSelectedDomainIndex(dataIndex); setSelectedDomainIndex(dataIndex);
logEvent('API Monitoring: Domain name row clicked', {});
} }
}, },
className: 'expanded-clickable-row', className: 'expanded-clickable-row',
@@ -147,6 +149,7 @@ function DomainList({
handleClose={(): void => { handleClose={(): void => {
setSelectedDomainIndex(-1); setSelectedDomainIndex(-1);
}} }}
domainListFilters={query?.filters}
/> />
)} )}
</section> </section>

View File

@@ -3,13 +3,14 @@ import './Explorer.styles.scss';
import { FilterOutlined } from '@ant-design/icons'; import { FilterOutlined } from '@ant-design/icons';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Switch, Typography } from 'antd'; import { Switch, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import cx from 'classnames'; import cx from 'classnames';
import QuickFilters from 'components/QuickFilters/QuickFilters'; import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource } from 'components/QuickFilters/types'; import { QuickFiltersSource } from 'components/QuickFilters/types';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations'; import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { Query } from 'types/api/queryBuilder/queryBuilderData'; import { Query } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
@@ -21,6 +22,10 @@ function Explorer(): JSX.Element {
const { currentQuery } = useQueryBuilder(); const { currentQuery } = useQueryBuilder();
useEffect(() => {
logEvent('API Monitoring: Landing page visited', {});
}, []);
const { handleChangeQueryData } = useQueryOperations({ const { handleChangeQueryData } = useQueryOperations({
index: 0, index: 0,
query: currentQuery.builder.queryData[0], query: currentQuery.builder.queryData[0],
@@ -64,7 +69,12 @@ function Explorer(): JSX.Element {
style={{ marginLeft: 'auto' }} style={{ marginLeft: 'auto' }}
checked={showIP} checked={showIP}
onClick={(): void => { onClick={(): void => {
setShowIP((showIP) => !showIP); setShowIP((showIP): boolean => {
logEvent('API Monitoring: Show IP addresses clicked', {
showIP: !showIP,
});
return !showIP;
});
}} }}
/> />
</div> </div>

View File

@@ -8,16 +8,23 @@ import {
} from 'components/QuickFilters/types'; } from 'components/QuickFilters/types';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys'; import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
import { GraphClickMetaData } from 'container/GridCardLayout/useNavigateToExplorerPages';
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults'; import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { ArrowUpDown, ChevronDown, ChevronRight } from 'lucide-react'; import { ArrowUpDown, ChevronDown, ChevronRight } from 'lucide-react';
import { getWidgetQuery } from 'pages/MessagingQueues/MQDetails/MetricPage/MetricPageUtil';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange'; import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { import {
BaseAutocompleteData, BaseAutocompleteData,
DataTypes, DataTypes,
} from 'types/api/queryBuilder/queryAutocompleteResponse'; } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData'; import {
IBuilderQuery,
TagFilterItem,
} from 'types/api/queryBuilder/queryBuilderData';
import { QueryData } from 'types/api/widgets/getQuery'; import { QueryData } from 'types/api/widgets/getQuery';
import { EQueryType } from 'types/common/dashboard'; import { EQueryType } from 'types/common/dashboard';
import { DataSource } from 'types/common/queryBuilder'; import { DataSource } from 'types/common/queryBuilder';
@@ -128,12 +135,15 @@ export const columnsConfig: ColumnType<APIDomainsRowData>[] = [
sorter: false, sorter: false,
align: 'right', align: 'right',
className: `column`, className: `column`,
render: (lastUsed: number): string => getLastUsedRelativeTime(lastUsed), render: (lastUsed: number | string): string =>
lastUsed === 'n/a' || lastUsed === '-'
? '-'
: getLastUsedRelativeTime(lastUsed as number),
}, },
{ {
title: ( title: (
<div> <div>
Rate <span className="round-metric-tag">/s</span> Rate <span className="round-metric-tag">ops/s</span>
</div> </div>
), ),
dataIndex: 'rate', dataIndex: 'rate',
@@ -155,21 +165,26 @@ export const columnsConfig: ColumnType<APIDomainsRowData>[] = [
sorter: false, sorter: false,
align: 'right', align: 'right',
className: `column`, className: `column`,
render: (errorRate: number): React.ReactNode => ( render: (errorRate: number | string): React.ReactNode => {
<Progress if (errorRate === 'n/a' || errorRate === '-') {
status="active" return '-';
percent={Number((errorRate * 100).toFixed(1))} }
strokeLinecap="butt" return (
size="small" <Progress
strokeColor={((): string => { status="active"
const errorRatePercent = Number((errorRate * 100).toFixed(1)); percent={Number(((errorRate as number) * 100).toFixed(1))}
if (errorRatePercent >= 90) return Color.BG_SAKURA_500; strokeLinecap="butt"
if (errorRatePercent >= 60) return Color.BG_AMBER_500; size="small"
return Color.BG_FOREST_500; strokeColor={((): string => {
})()} const errorRatePercent = Number(((errorRate as number) * 100).toFixed(1));
className="progress-bar error-rate" if (errorRatePercent >= 90) return Color.BG_SAKURA_500;
/> if (errorRatePercent >= 60) return Color.BG_AMBER_500;
), return Color.BG_FOREST_500;
})()}
className="progress-bar error-rate"
/>
);
},
}, },
{ {
title: ( title: (
@@ -217,9 +232,9 @@ interface APIMonitoringResponseRow {
data: { data: {
endpoints: number; endpoints: number;
error_rate: number; error_rate: number;
lastseen: number; lastseen: number | string;
[domainNameKey]: string; [domainNameKey]: string;
p99: number; p99: number | string;
rps: number; rps: number;
}; };
} }
@@ -232,12 +247,12 @@ interface EndPointsResponseRow {
export interface APIDomainsRowData { export interface APIDomainsRowData {
key: string; key: string;
domainName: React.ReactNode; domainName: string;
endpointCount: React.ReactNode; endpointCount: number | string;
rate: React.ReactNode; rate: number | string;
errorRate: React.ReactNode; errorRate: number | string;
latency: React.ReactNode; latency: number | string;
lastUsed: React.ReactNode; lastUsed: string;
} }
// Rename this to a proper name // Rename this to a proper name
@@ -246,12 +261,20 @@ export const formatDataForTable = (
): APIDomainsRowData[] => ): APIDomainsRowData[] =>
data?.map((domain) => ({ data?.map((domain) => ({
key: v4(), key: v4(),
domainName: domain.data[domainNameKey] || '', domainName: domain?.data[domainNameKey] || '-',
endpointCount: domain.data.endpoints, endpointCount: domain?.data?.endpoints || '-',
rate: domain.data.rps, rate: domain.data.rps || '-',
errorRate: domain.data.error_rate, errorRate: domain.data.error_rate || '-',
latency: Math.round(domain.data.p99 / 1000000), // Convert from nanoseconds to milliseconds latency:
lastUsed: new Date(Math.floor(domain.data.lastseen / 1000000)).toISOString(), // Convert from nanoseconds to milliseconds domain.data.p99 === 'n/a'
? '-'
: Math.round(Number(domain.data.p99) / 1000000), // Convert from nanoseconds to milliseconds
lastUsed:
domain.data.lastseen === 'n/a'
? '-'
: new Date(
Math.floor(Number(domain.data.lastseen) / 1000000),
).toISOString(), // Convert from nanoseconds to milliseconds
})); }));
// Rename this to a proper name // Rename this to a proper name
@@ -468,7 +491,6 @@ export const extractPortAndEndpoint = (
} }
}; };
// Add icons in the below column headers
export const getEndPointsColumnsConfig = ( export const getEndPointsColumnsConfig = (
isGroupedByAttribute: boolean, isGroupedByAttribute: boolean,
expandedRowKeys: React.Key[], expandedRowKeys: React.Key[],
@@ -576,7 +598,7 @@ export const formatEndPointsDataForTable = (
); );
return { return {
key: v4(), key: v4(),
endpointName: (endpoint.data['http.url'] as string) || '', endpointName: (endpoint.data['http.url'] as string) || '-',
port, port,
callCount: endpoint.data.A || '-', callCount: endpoint.data.A || '-',
latency: latency:
@@ -593,7 +615,6 @@ export const formatEndPointsDataForTable = (
const groupedByAttributeData = groupBy.map((attribute) => attribute.key); const groupedByAttributeData = groupBy.map((attribute) => attribute.key);
// TODO: Use tags to show the concatenated attribute values
return data?.map((endpoint) => { return data?.map((endpoint) => {
const newEndpointName = groupedByAttributeData const newEndpointName = groupedByAttributeData
.map((attribute) => endpoint.data[attribute]) .map((attribute) => endpoint.data[attribute])
@@ -639,7 +660,7 @@ export const createFiltersForSelectedRowData = (
type: null, type: null,
}, },
op: '=', op: '=',
value: groupedByMeta[key], value: groupedByMeta[key] || '',
id: key, id: key,
})), })),
); );
@@ -649,12 +670,10 @@ export const createFiltersForSelectedRowData = (
// First query payload for endpoint metrics // First query payload for endpoint metrics
// Second query payload for endpoint status code // Second query payload for endpoint status code
// Third query payload for endpoint rate over time graph // Third query payload for endpoint dropdown selection
// Fourth query payload for endpoint latency over time graph // Fourth query payload for endpoint dependant services
// Fifth query payload for endpoint dropdown selection // Fifth query payload for endpoint response status count bar chart
// Sixth query payload for endpoint dependant services // Sixth query payload for endpoint response status code latency bar chart
// Seventh query payload for endpoint response status count bar chart
// Eighth query payload for endpoint response status code latency bar chart
export const getEndPointDetailsQueryPayload = ( export const getEndPointDetailsQueryPayload = (
domainName: string, domainName: string,
endPointName: string, endPointName: string,
@@ -1101,205 +1120,6 @@ export const getEndPointDetailsQueryPayload = (
end, end,
step: 60, step: 60,
}, },
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.String,
id: '------false',
isColumn: false,
key: '',
type: '',
},
aggregateOperator: 'rate',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'B',
filters: {
items: [
{
id: '3c76fe0b',
key: {
dataType: DataTypes.String,
id: 'net.peer.name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'net.peer.name',
type: 'tag',
},
op: '=',
value: domainName,
},
{
id: '30710f04',
key: {
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
op: '=',
value: endPointName,
},
...filters.items,
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
],
having: [],
legend: '',
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: false,
start,
end,
step: 60,
},
{
selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TIME_SERIES,
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'duration_nano--float64----true',
isColumn: true,
isJSON: false,
key: 'duration_nano',
type: '',
},
aggregateOperator: 'p99',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'B',
filters: {
items: [
{
id: '63adb3ff',
key: {
dataType: DataTypes.String,
id: 'net.peer.name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'net.peer.name',
type: 'tag',
},
op: '=',
value: domainName,
},
{
id: '50142500',
key: {
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
op: '=',
value: endPointName,
},
...filters.items,
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
],
having: [],
legend: '',
limit: null,
orderBy: [],
queryName: 'B',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'p99',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},
variables: {},
formatForWeb: false,
start,
end,
step: 60,
},
{ {
selectedTime: 'GLOBAL_TIME', selectedTime: 'GLOBAL_TIME',
graphType: PANEL_TYPES.TABLE, graphType: PANEL_TYPES.TABLE,
@@ -1801,7 +1621,7 @@ interface EndPointMetricsData {
interface EndPointStatusCodeData { interface EndPointStatusCodeData {
key: string; key: string;
statusCode: string; statusCode: string;
count: number; count: number | string;
p99Latency: number | string; p99Latency: number | string;
} }
@@ -1824,8 +1644,8 @@ export const getFormattedEndPointStatusCodeData = (
): EndPointStatusCodeData[] => ): EndPointStatusCodeData[] =>
data?.map((row) => ({ data?.map((row) => ({
key: v4(), key: v4(),
statusCode: row.data.response_status_code, statusCode: row.data.response_status_code || '-',
count: row.data.A, count: row.data.A || '-',
p99Latency: p99Latency:
row.data.B === 'n/a' ? '-' : Math.round(Number(row.data.B) / 1000000), // Convert from nanoseconds to milliseconds, row.data.B === 'n/a' ? '-' : Math.round(Number(row.data.B) / 1000000), // Convert from nanoseconds to milliseconds,
})); }));
@@ -1857,11 +1677,6 @@ export const endPointStatusCodeColumns: ColumnType<EndPointStatusCodeData>[] = [
}, },
]; ];
export const apiWidgetInfo = [
{ title: 'Rate over time', yAxisUnit: 'ops/s' },
{ title: 'Latency over time', yAxisUnit: 'ns' },
];
export const statusCodeWidgetInfo = [ export const statusCodeWidgetInfo = [
{ yAxisUnit: 'calls' }, { yAxisUnit: 'calls' },
{ yAxisUnit: 'ns' }, { yAxisUnit: 'ns' },
@@ -1885,8 +1700,8 @@ export const getFormattedEndPointDropDownData = (
): EndPointDropDownData[] => ): EndPointDropDownData[] =>
data?.map((row) => ({ data?.map((row) => ({
key: v4(), key: v4(),
label: row.data['http.url'], label: row.data['http.url'] || '-',
value: row.data['http.url'], value: row.data['http.url'] || '-',
})); }));
interface DependentServicesResponseRow { interface DependentServicesResponseRow {
@@ -1903,6 +1718,7 @@ interface DependentServicesData {
percentage: number; percentage: number;
} }
// Discuss once about type safety of this function
export const getFormattedDependentServicesData = ( export const getFormattedDependentServicesData = (
data: DependentServicesResponseRow[], data: DependentServicesResponseRow[],
): DependentServicesData[] => { ): DependentServicesData[] => {
@@ -1983,7 +1799,7 @@ export const groupStatusCodes = (
// Track all timestamps // Track all timestamps
series.values.forEach((value) => { series.values.forEach((value) => {
allTimestamps.add(value[0]); allTimestamps.add(Number(value[0]));
}); });
// Initialize or update the grouped series // Initialize or update the grouped series
@@ -2049,8 +1865,114 @@ export const groupStatusCodes = (
}); });
}); });
return Object.values(groupedSeries); // Define the order of status code ranges
const statusCodeOrder = ['200-299', '300-399', '400-499', '500-599', 'Other'];
// Return the grouped series in the specified order
return statusCodeOrder
.filter((code) => groupedSeries[code]) // Only include codes that exist in the data
.map((code) => groupedSeries[code]);
}; };
export const getStatusCodeBarChartWidgetData = (
domainName: string,
endPointName: string,
filters: IBuilderQuery['filters'],
): Widgets => ({
query: {
builder: {
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.String,
id: '------false',
isColumn: false,
key: '',
type: '',
},
aggregateOperator: 'count',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: 'c6724407',
key: {
dataType: DataTypes.String,
id: 'net.peer.name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'net.peer.name',
type: 'tag',
},
op: '=',
value: domainName,
},
{
id: '8b1be6f0',
key: {
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
op: '=',
value: endPointName,
},
...filters.items,
],
op: 'AND',
},
functions: [],
groupBy: [],
having: [],
legend: '',
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
},
],
queryFormulas: [],
},
clickhouse_sql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
promql: [
{
disabled: false,
legend: '',
name: 'A',
query: '',
},
],
queryType: EQueryType.QUERY_BUILDER,
},
description: '',
id: '315b15fa-ff0c-442f-89f8-2bf4fb1af2f2',
isStacked: false,
panelTypes: PANEL_TYPES.BAR,
title: '',
opacity: '',
nullZeroValues: '',
timePreferance: 'GLOBAL_TIME',
softMin: null,
softMax: null,
selectedLogFields: null,
selectedTracesFields: null,
});
interface EndPointStatusCodePayloadData { interface EndPointStatusCodePayloadData {
data: { data: {
result: QueryData[]; result: QueryData[];
@@ -2085,3 +2007,277 @@ export const END_POINT_DETAILS_QUERY_KEYS_ARRAY = [
REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_BAR_CHARTS_DATA, REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_BAR_CHARTS_DATA,
REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA, REACT_QUERY_KEY.GET_ENDPOINT_STATUS_CODE_LATENCY_BAR_CHARTS_DATA,
]; ];
export const getRateOverTimeWidgetData = (
domainName: string,
endPointName: string,
filters: IBuilderQuery['filters'],
): Widgets => {
const { endpoint, port } = extractPortAndEndpoint(endPointName);
const legend = `${
port !== '-' && port !== 'n/a' ? `${port}:` : ''
}${endpoint}`;
return getWidgetQueryBuilder(
getWidgetQuery({
title: 'Rate Over Time',
description: 'Rate over time.',
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.String,
id: '------false',
isColumn: false,
key: '',
type: '',
},
aggregateOperator: 'rate',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: '3c76fe0b',
key: {
dataType: DataTypes.String,
id: 'net.peer.name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'net.peer.name',
type: 'tag',
},
op: '=',
value: domainName,
},
{
id: '30710f04',
key: {
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
op: '=',
value: endPointName,
},
...filters.items,
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
],
having: [],
legend,
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'rate',
},
],
yAxisUnit: 'ops/s',
}),
);
};
export const getLatencyOverTimeWidgetData = (
domainName: string,
endPointName: string,
filters: IBuilderQuery['filters'],
): Widgets => {
const { endpoint, port } = extractPortAndEndpoint(endPointName);
const legend = `${port}:${endpoint}`;
return getWidgetQueryBuilder(
getWidgetQuery({
title: 'Latency Over Time',
description: 'Latency over time.',
queryData: [
{
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'duration_nano--float64----true',
isColumn: true,
isJSON: false,
key: 'duration_nano',
type: '',
},
aggregateOperator: 'p99',
dataSource: DataSource.TRACES,
disabled: false,
expression: 'A',
filters: {
items: [
{
id: '63adb3ff',
key: {
dataType: DataTypes.String,
id: 'net.peer.name--string--tag--false',
isColumn: false,
isJSON: false,
key: 'net.peer.name',
type: 'tag',
},
op: '=',
value: domainName,
},
{
id: '50142500',
key: {
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
op: '=',
value: endPointName,
},
...filters.items,
],
op: 'AND',
},
functions: [],
groupBy: [
{
dataType: DataTypes.String,
id: 'http.url--string--tag--false',
isColumn: false,
isJSON: false,
key: 'http.url',
type: 'tag',
},
],
having: [],
legend,
limit: null,
orderBy: [],
queryName: 'A',
reduceTo: 'avg',
spaceAggregation: 'sum',
stepInterval: 60,
timeAggregation: 'p99',
},
],
yAxisUnit: 'ns',
}),
);
};
/**
* Helper function to get the start and end status codes from a status code range string
* @param value Status code range string (e.g. '200-299') or boolean
* @returns Tuple of [startStatusCode, endStatusCode] as strings
*/
const getStartAndEndStatusCode = (
value: string | boolean,
): [string, string] => {
if (!value) {
return ['', ''];
}
switch (value) {
case '100-199':
return ['100', '199'];
case '200-299':
return ['200', '299'];
case '300-399':
return ['300', '399'];
case '400-499':
return ['400', '499'];
case '500-599':
return ['500', '599'];
default:
return ['', ''];
}
};
/**
* Creates filter items for bar chart based on group by fields and request data
* Used specifically for filtering status code ranges in bar charts
* @param groupBy Array of group by fields to create filters for
* @param requestData Data from graph click containing values to filter on
* @returns Array of TagFilterItems with >= and < operators for status code ranges
*/
export const createGroupByFiltersForBarChart = (
groupBy: BaseAutocompleteData[],
requestData: GraphClickMetaData,
): TagFilterItem[] =>
groupBy
.map((gb) => {
const value = requestData[gb.key];
const [startStatusCode, endStatusCode] = getStartAndEndStatusCode(value);
return value
? [
{
id: v4(),
key: gb,
op: '>=',
value: startStatusCode,
},
{
id: v4(),
key: gb,
op: '<=',
value: endStatusCode,
},
]
: [];
})
.flat();
export const getCustomFiltersForBarChart = (
metric:
| {
[key: string]: string;
}
| undefined,
): TagFilterItem[] => {
if (!metric?.response_status_code) {
return [];
}
const [startStatusCode, endStatusCode] = getStartAndEndStatusCode(
metric.response_status_code,
);
return [
{
id: v4(),
key: {
dataType: DataTypes.String,
id: 'response_status_code--string--tag--false',
isColumn: false,
isJSON: false,
key: 'response_status_code',
type: 'tag',
},
op: '>=',
value: startStatusCode,
},
{
id: v4(),
key: {
dataType: DataTypes.String,
id: 'response_status_code--string--tag--false',
isColumn: false,
isJSON: false,
key: 'response_status_code',
type: 'tag',
},
op: '<=',
value: endStatusCode,
},
];
};

View File

@@ -61,7 +61,7 @@ export default function CustomDomainSettings(): JSX.Element {
isLoading: isLoadingDeploymentsData, isLoading: isLoadingDeploymentsData,
isFetching: isFetchingDeploymentsData, isFetching: isFetchingDeploymentsData,
refetch: refetchDeploymentsData, refetch: refetchDeploymentsData,
} = useGetDeploymentsData(); } = useGetDeploymentsData(true);
const { const {
mutate: updateSubDomain, mutate: updateSubDomain,

View File

@@ -1,4 +1,5 @@
import { Form, Input } from 'antd'; import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -9,7 +10,20 @@ function MsTeams({ setSelectedConfig }: MsTeamsProps): JSX.Element {
return ( return (
<> <>
<Form.Item name="webhook_url" label={t('field_webhook_url')}> <Form.Item
name="webhook_url"
label={t('field_webhook_url')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_ms_teams_url')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
>
<Input <Input
onChange={(event): void => { onChange={(event): void => {
setSelectedConfig((value) => ({ setSelectedConfig((value) => ({

View File

@@ -1,4 +1,5 @@
import { Form, Input } from 'antd'; import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { OpsgenieChannel } from '../../CreateAlertChannels/config'; import { OpsgenieChannel } from '../../CreateAlertChannels/config';
@@ -19,7 +20,21 @@ function OpsgenieForm({ setSelectedConfig }: OpsgenieFormProps): JSX.Element {
return ( return (
<> <>
<Form.Item name="api_key" label={t('field_opsgenie_api_key')} required> <Form.Item
name="api_key"
label={t('field_opsgenie_api_key')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_opsgenie_api_key')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
required
>
<Input <Input
onChange={handleInputChange('api_key')} onChange={handleInputChange('api_key')}
data-testid="opsgenie-api-key-textbox" data-testid="opsgenie-api-key-textbox"

View File

@@ -1,4 +1,5 @@
import { Form, Input } from 'antd'; import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -10,7 +11,20 @@ function PagerForm({ setSelectedConfig }: PagerFormProps): JSX.Element {
const { t } = useTranslation('channels'); const { t } = useTranslation('channels');
return ( return (
<> <>
<Form.Item name="routing_key" label={t('field_pager_routing_key')} required> <Form.Item
name="routing_key"
label={t('field_pager_routing_key')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_pager_routing_key')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
>
<Input <Input
onChange={(event): void => { onChange={(event): void => {
setSelectedConfig((value) => ({ setSelectedConfig((value) => ({

View File

@@ -1,4 +1,5 @@
import { Form, Input } from 'antd'; import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -11,7 +12,20 @@ function Slack({ setSelectedConfig }: SlackProps): JSX.Element {
return ( return (
<> <>
<Form.Item name="api_url" label={t('field_webhook_url')}> <Form.Item
name="api_url"
label={t('field_webhook_url')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_slack_url')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
>
<Input <Input
onChange={(event): void => { onChange={(event): void => {
setSelectedConfig((value) => ({ setSelectedConfig((value) => ({

View File

@@ -1,4 +1,5 @@
import { Form, Input } from 'antd'; import { Form, Input } from 'antd';
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -9,7 +10,20 @@ function WebhookSettings({ setSelectedConfig }: WebhookProps): JSX.Element {
return ( return (
<> <>
<Form.Item name="api_url" label={t('field_webhook_url')}> <Form.Item
name="api_url"
label={t('field_webhook_url')}
tooltip={{
title: (
<MarkdownRenderer
markdownContent={t('tooltip_webhook_url')}
variables={{}}
/>
),
overlayInnerStyle: { maxWidth: 400 },
placement: 'right',
}}
>
<Input <Input
onChange={(event): void => { onChange={(event): void => {
setSelectedConfig((value) => ({ setSelectedConfig((value) => ({

View File

@@ -1,6 +1,5 @@
import { Form, FormInstance, Input, Select, Switch, Typography } from 'antd'; import { Form, FormInstance, Input, Select, Switch, Typography } from 'antd';
import { Store } from 'antd/lib/form/interface'; import { Store } from 'antd/lib/form/interface';
import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import { import {
ChannelType, ChannelType,
@@ -11,11 +10,8 @@ import {
WebhookChannel, WebhookChannel,
} from 'container/CreateAlertChannels/config'; } from 'container/CreateAlertChannels/config';
import history from 'lib/history'; import history from 'lib/history';
import { useAppContext } from 'providers/App/App';
import { Dispatch, ReactElement, SetStateAction } from 'react'; import { Dispatch, ReactElement, SetStateAction } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
import { isFeatureKeys } from 'utils/app';
import EmailSettings from './Settings/Email'; import EmailSettings from './Settings/Email';
import MsTeamsSettings from './Settings/MsTeams'; import MsTeamsSettings from './Settings/MsTeams';
@@ -39,17 +35,6 @@ function FormAlertChannels({
editing = false, editing = false,
}: FormAlertChannelsProps): JSX.Element { }: FormAlertChannelsProps): JSX.Element {
const { t } = useTranslation('channels'); const { t } = useTranslation('channels');
const { featureFlags } = useAppContext();
const feature = `ALERT_CHANNEL_${type.toUpperCase()}`;
const featureKey = isFeatureKeys(feature)
? feature
: FeatureKeys.ALERT_CHANNEL_SLACK;
const hasFeature = featureFlags?.find(
(flag: FeatureFlagProps) => flag.name === featureKey,
);
const renderSettings = (): ReactElement | null => { const renderSettings = (): ReactElement | null => {
switch (type) { switch (type) {
@@ -146,7 +131,7 @@ function FormAlertChannels({
<Form.Item> <Form.Item>
<Button <Button
disabled={savingState || !hasFeature} disabled={savingState}
loading={savingState} loading={savingState}
type="primary" type="primary"
onClick={(): void => onSaveHandler(type)} onClick={(): void => onSaveHandler(type)}
@@ -154,7 +139,7 @@ function FormAlertChannels({
{t('button_save_channel')} {t('button_save_channel')}
</Button> </Button>
<Button <Button
disabled={testingState || !hasFeature} disabled={testingState}
loading={testingState} loading={testingState}
onClick={(): void => onTestHandler(type)} onClick={(): void => onTestHandler(type)}
> >

View File

@@ -467,10 +467,6 @@ function FormAlertRules({
panelType, panelType,
]); ]);
const isAlertAvailable =
!featureFlags?.find((flag) => flag.name === FeatureKeys.QUERY_BUILDER_ALERTS)
?.active || false;
const saveRule = useCallback(async () => { const saveRule = useCallback(async () => {
if (!isFormValid()) { if (!isFormValid()) {
return; return;
@@ -688,11 +684,6 @@ function FormAlertRules({
const isAlertNameMissing = !formInstance.getFieldValue('alert'); const isAlertNameMissing = !formInstance.getFieldValue('alert');
const isAlertAvailableToSave =
isAlertAvailable &&
currentQuery.queryType === EQueryType.QUERY_BUILDER &&
alertType !== AlertTypes.METRICS_BASED_ALERT;
const onUnitChangeHandler = (value: string): void => { const onUnitChangeHandler = (value: string): void => {
setYAxisUnit(value); setYAxisUnit(value);
// reset target unit // reset target unit
@@ -865,7 +856,6 @@ function FormAlertRules({
icon={<SaveOutlined />} icon={<SaveOutlined />}
disabled={ disabled={
isAlertNameMissing || isAlertNameMissing ||
isAlertAvailableToSave ||
!isChannelConfigurationValid || !isChannelConfigurationValid ||
queryStatus === 'error' queryStatus === 'error'
} }

View File

@@ -49,6 +49,7 @@ function FullView({
isDependedDataLoaded = false, isDependedDataLoaded = false,
onToggleModelHandler, onToggleModelHandler,
onClickHandler, onClickHandler,
customOnDragSelect,
setCurrentGraphRef, setCurrentGraphRef,
}: FullViewProps): JSX.Element { }: FullViewProps): JSX.Element {
const { safeNavigate } = useSafeNavigate(); const { safeNavigate } = useSafeNavigate();
@@ -252,7 +253,7 @@ function FullView({
onToggleModelHandler={onToggleModelHandler} onToggleModelHandler={onToggleModelHandler}
setGraphVisibility={setGraphsVisibilityStates} setGraphVisibility={setGraphsVisibilityStates}
graphVisibility={graphsVisibilityStates} graphVisibility={graphsVisibilityStates}
onDragSelect={onDragSelect} onDragSelect={customOnDragSelect ?? onDragSelect}
tableProcessedDataRef={tableProcessedDataRef} tableProcessedDataRef={tableProcessedDataRef}
searchTerm={searchTerm} searchTerm={searchTerm}
onClickHandler={onClickHandler} onClickHandler={onClickHandler}

View File

@@ -50,6 +50,7 @@ export interface FullViewProps {
widget: Widgets; widget: Widgets;
fullViewOptions?: boolean; fullViewOptions?: boolean;
onClickHandler?: OnClickPluginOpts['onClick']; onClickHandler?: OnClickPluginOpts['onClick'];
customOnDragSelect?: (start: number, end: number) => void;
name: string; name: string;
tableProcessedDataRef: MutableRefObject<RowData[]>; tableProcessedDataRef: MutableRefObject<RowData[]>;
version?: string; version?: string;

View File

@@ -50,6 +50,7 @@ function WidgetGraphComponent({
setRequestData, setRequestData,
onClickHandler, onClickHandler,
onDragSelect, onDragSelect,
customOnDragSelect,
customTooltipElement, customTooltipElement,
openTracesButton, openTracesButton,
onOpenTraceBtnClick, onOpenTraceBtnClick,
@@ -327,6 +328,7 @@ function WidgetGraphComponent({
onToggleModelHandler={onToggleModelHandler} onToggleModelHandler={onToggleModelHandler}
tableProcessedDataRef={tableProcessedDataRef} tableProcessedDataRef={tableProcessedDataRef}
onClickHandler={onClickHandler ?? graphClickHandler} onClickHandler={onClickHandler ?? graphClickHandler}
customOnDragSelect={customOnDragSelect}
setCurrentGraphRef={setCurrentGraphRef} setCurrentGraphRef={setCurrentGraphRef}
/> />
</Modal> </Modal>

View File

@@ -36,6 +36,7 @@ function GridCardGraph({
version, version,
onClickHandler, onClickHandler,
onDragSelect, onDragSelect,
customOnDragSelect,
customTooltipElement, customTooltipElement,
dataAvailable, dataAvailable,
getGraphData, getGraphData,
@@ -272,6 +273,7 @@ function GridCardGraph({
setRequestData={setRequestData} setRequestData={setRequestData}
onClickHandler={onClickHandler} onClickHandler={onClickHandler}
onDragSelect={onDragSelect} onDragSelect={onDragSelect}
customOnDragSelect={customOnDragSelect}
customTooltipElement={customTooltipElement} customTooltipElement={customTooltipElement}
openTracesButton={openTracesButton} openTracesButton={openTracesButton}
onOpenTraceBtnClick={onOpenTraceBtnClick} onOpenTraceBtnClick={onOpenTraceBtnClick}

View File

@@ -33,6 +33,7 @@ export interface WidgetGraphComponentProps {
setRequestData?: Dispatch<SetStateAction<GetQueryResultsProps>>; setRequestData?: Dispatch<SetStateAction<GetQueryResultsProps>>;
onClickHandler?: OnClickPluginOpts['onClick']; onClickHandler?: OnClickPluginOpts['onClick'];
onDragSelect: (start: number, end: number) => void; onDragSelect: (start: number, end: number) => void;
customOnDragSelect?: (start: number, end: number) => void;
customTooltipElement?: HTMLDivElement; customTooltipElement?: HTMLDivElement;
openTracesButton?: boolean; openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void; onOpenTraceBtnClick?: (record: RowData) => void;
@@ -49,6 +50,7 @@ export interface GridCardGraphProps {
variables?: Dashboard['data']['variables']; variables?: Dashboard['data']['variables'];
version?: string; version?: string;
onDragSelect: (start: number, end: number) => void; onDragSelect: (start: number, end: number) => void;
customOnDragSelect?: (start: number, end: number) => void;
customTooltipElement?: HTMLDivElement; customTooltipElement?: HTMLDivElement;
dataAvailable?: (isDataAvailable: boolean) => void; dataAvailable?: (isDataAvailable: boolean) => void;
getGraphData?: (graphData?: MetricRangePayloadProps['data']) => void; getGraphData?: (graphData?: MetricRangePayloadProps['data']) => void;

View File

@@ -178,6 +178,7 @@ interface HandleGraphClickParams {
navigateToExplorer: (props: NavigateToExplorerProps) => void; navigateToExplorer: (props: NavigateToExplorerProps) => void;
notifications: NotificationInstance; notifications: NotificationInstance;
graphClick: (props: GraphClickProps) => void; graphClick: (props: GraphClickProps) => void;
customFilters?: TagFilterItem[];
} }
export const handleGraphClick = async ({ export const handleGraphClick = async ({
@@ -192,6 +193,7 @@ export const handleGraphClick = async ({
navigateToExplorer, navigateToExplorer,
notifications, notifications,
graphClick, graphClick,
customFilters,
}: HandleGraphClickParams): Promise<void> => { }: HandleGraphClickParams): Promise<void> => {
const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {}; const { stepInterval } = widget?.query?.builder?.queryData?.[0] ?? {};
@@ -221,7 +223,7 @@ export const handleGraphClick = async ({
}: ${key}`, }: ${key}`,
onClick: (): void => onClick: (): void =>
navigateToExplorer({ navigateToExplorer({
filters: result[key].filters, filters: [...result[key].filters, ...(customFilters || [])],
dataSource: result[key].dataSource as DataSource, dataSource: result[key].dataSource as DataSource,
startTime: xValue, startTime: xValue,
endTime: xValue + (stepInterval ?? 60), endTime: xValue + (stepInterval ?? 60),

View File

@@ -44,7 +44,10 @@ import { EditMenuAction, ViewMenuAction } from './config';
import DashboardEmptyState from './DashboardEmptyState/DashboardEmptyState'; import DashboardEmptyState from './DashboardEmptyState/DashboardEmptyState';
import GridCard from './GridCard'; import GridCard from './GridCard';
import { Card, CardContainer, ReactGridLayout } from './styles'; import { Card, CardContainer, ReactGridLayout } from './styles';
import { removeUndefinedValuesFromLayout } from './utils'; import {
hasColumnWidthsChanged,
removeUndefinedValuesFromLayout,
} from './utils';
import { MenuItemKeys } from './WidgetHeader/contants'; import { MenuItemKeys } from './WidgetHeader/contants';
import { WidgetRowHeader } from './WidgetRow'; import { WidgetRowHeader } from './WidgetRow';
@@ -68,6 +71,7 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
setDashboardQueryRangeCalled, setDashboardQueryRangeCalled,
setSelectedRowWidgetId, setSelectedRowWidgetId,
isDashboardFetching, isDashboardFetching,
columnWidths,
} = useDashboard(); } = useDashboard();
const { data } = selectedDashboard || {}; const { data } = selectedDashboard || {};
const { pathname } = useLocation(); const { pathname } = useLocation();
@@ -162,6 +166,7 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
logEventCalledRef.current = true; logEventCalledRef.current = true;
} }
}, [data]); }, [data]);
const onSaveHandler = (): void => { const onSaveHandler = (): void => {
if (!selectedDashboard) return; if (!selectedDashboard) return;
@@ -171,6 +176,15 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
...selectedDashboard.data, ...selectedDashboard.data,
panelMap: { ...currentPanelMap }, panelMap: { ...currentPanelMap },
layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET), layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
widgets: selectedDashboard?.data?.widgets?.map((widget) => {
if (columnWidths?.[widget.id]) {
return {
...widget,
columnWidths: columnWidths[widget.id],
};
}
return widget;
}),
}, },
uuid: selectedDashboard.uuid, uuid: selectedDashboard.uuid,
}; };
@@ -227,20 +241,31 @@ function GraphLayout(props: GraphLayoutProps): JSX.Element {
useEffect(() => { useEffect(() => {
if ( if (
isDashboardLocked ||
!saveLayoutPermission ||
updateDashboardMutation.isLoading ||
isDashboardFetching
) {
return;
}
const shouldSaveLayout =
dashboardLayout && dashboardLayout &&
Array.isArray(dashboardLayout) && Array.isArray(dashboardLayout) &&
dashboardLayout.length > 0 && dashboardLayout.length > 0 &&
!isEqual(layouts, dashboardLayout) && !isEqual(layouts, dashboardLayout);
!isDashboardLocked &&
saveLayoutPermission && const shouldSaveColumnWidths =
!updateDashboardMutation.isLoading && dashboardLayout &&
!isDashboardFetching Array.isArray(dashboardLayout) &&
) { dashboardLayout.length > 0 &&
hasColumnWidthsChanged(columnWidths, selectedDashboard);
if (shouldSaveLayout || shouldSaveColumnWidths) {
onSaveHandler(); onSaveHandler();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [dashboardLayout]); }, [dashboardLayout, columnWidths]);
const onSettingsModalSubmit = (): void => { const onSettingsModalSubmit = (): void => {
const newTitle = form.getFieldValue('title'); const newTitle = form.getFieldValue('title');

View File

@@ -12,7 +12,7 @@ import { v4 } from 'uuid';
import { extractQueryNamesFromExpression } from './utils'; import { extractQueryNamesFromExpression } from './utils';
type GraphClickMetaData = { export type GraphClickMetaData = {
[key: string]: string | boolean; [key: string]: string | boolean;
queryName: string; queryName: string;
inFocusOrNot: boolean; inFocusOrNot: boolean;

View File

@@ -1,5 +1,7 @@
import { FORMULA_REGEXP } from 'constants/regExp'; import { FORMULA_REGEXP } from 'constants/regExp';
import { isEmpty, isEqual } from 'lodash-es';
import { Layout } from 'react-grid-layout'; import { Layout } from 'react-grid-layout';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
export const removeUndefinedValuesFromLayout = (layout: Layout[]): Layout[] => export const removeUndefinedValuesFromLayout = (layout: Layout[]): Layout[] =>
layout.map((obj) => layout.map((obj) =>
@@ -25,3 +27,27 @@ export function extractQueryNamesFromExpression(expression: string): string[] {
// Extract matches and deduplicate // Extract matches and deduplicate
return [...new Set(expression.match(queryNameRegex) || [])]; return [...new Set(expression.match(queryNameRegex) || [])];
} }
export const hasColumnWidthsChanged = (
columnWidths: Record<string, Record<string, number>>,
selectedDashboard?: Dashboard,
): boolean => {
// If no column widths stored, no changes
if (isEmpty(columnWidths) || !selectedDashboard) return false;
// Check each widget's column widths
return Object.keys(columnWidths).some((widgetId) => {
const dashboardWidget = selectedDashboard?.data?.widgets?.find(
(widget) => widget.id === widgetId,
) as Widgets;
const newWidths = columnWidths[widgetId];
const existingWidths = dashboardWidget?.columnWidths;
// If both are empty/undefined, no change
if (isEmpty(newWidths) || isEmpty(existingWidths)) return false;
// Compare stored column widths with dashboard widget's column widths
return !isEqual(newWidths, existingWidths);
});
};

View File

@@ -43,6 +43,7 @@ function GridTableComponent({
sticky, sticky,
openTracesButton, openTracesButton,
onOpenTraceBtnClick, onOpenTraceBtnClick,
widgetId,
...props ...props
}: GridTableComponentProps): JSX.Element { }: GridTableComponentProps): JSX.Element {
const { t } = useTranslation(['valueGraph']); const { t } = useTranslation(['valueGraph']);
@@ -229,6 +230,7 @@ function GridTableComponent({
columns={openTracesButton ? columnDataWithOpenTracesButton : newColumnData} columns={openTracesButton ? columnDataWithOpenTracesButton : newColumnData}
dataSource={dataSource} dataSource={dataSource}
sticky={sticky} sticky={sticky}
widgetId={widgetId}
onRow={ onRow={
openTracesButton openTracesButton
? (record): React.HTMLAttributes<HTMLElement> => ({ ? (record): React.HTMLAttributes<HTMLElement> => ({

View File

@@ -17,6 +17,7 @@ export type GridTableComponentProps = {
searchTerm?: string; searchTerm?: string;
openTracesButton?: boolean; openTracesButton?: boolean;
onOpenTraceBtnClick?: (record: RowData) => void; onOpenTraceBtnClick?: (record: RowData) => void;
widgetId?: string;
} & Pick<LogsExplorerTableProps, 'data'> & } & Pick<LogsExplorerTableProps, 'data'> &
Omit<TableProps<RowData>, 'columns' | 'dataSource'>; Omit<TableProps<RowData>, 'columns' | 'dataSource'>;

View File

@@ -23,10 +23,13 @@ function DataSourceInfo({
const notSendingData = !dataSentToSigNoz; const notSendingData = !dataSentToSigNoz;
const isEnabled =
activeLicenseV3 && activeLicenseV3.platform === LicensePlatform.CLOUD;
const { const {
data: deploymentsData, data: deploymentsData,
isError: isErrorDeploymentsData, isError: isErrorDeploymentsData,
} = useGetDeploymentsData(); } = useGetDeploymentsData(isEnabled || false);
const [region, setRegion] = useState<string>(''); const [region, setRegion] = useState<string>('');
const [url, setUrl] = useState<string>(''); const [url, setUrl] = useState<string>('');

View File

@@ -293,7 +293,7 @@ function MultiIngestionSettings(): JSX.Element {
isLoading: isLoadingDeploymentsData, isLoading: isLoadingDeploymentsData,
isFetching: isFetchingDeploymentsData, isFetching: isFetchingDeploymentsData,
isError: isErrorDeploymentsData, isError: isErrorDeploymentsData,
} = useGetDeploymentsData(); } = useGetDeploymentsData(true);
const { const {
mutate: createIngestionKey, mutate: createIngestionKey,

View File

@@ -10,7 +10,6 @@ import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQue
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder'; import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import useDebouncedFn from 'hooks/useDebouncedFunction'; import useDebouncedFn from 'hooks/useDebouncedFunction';
import { useEventSourceEvent } from 'hooks/useEventSourceEvent'; import { useEventSourceEvent } from 'hooks/useEventSourceEvent';
import { useNotifications } from 'hooks/useNotifications';
import { prepareQueryRangePayload } from 'lib/dashboard/prepareQueryRangePayload'; import { prepareQueryRangePayload } from 'lib/dashboard/prepareQueryRangePayload';
import { useEventSource } from 'providers/EventSource'; import { useEventSource } from 'providers/EventSource';
import { useCallback, useEffect, useRef, useState } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react';
@@ -38,8 +37,6 @@ function LiveLogsContainer(): JSX.Element {
const batchedEventsRef = useRef<ILog[]>([]); const batchedEventsRef = useRef<ILog[]>([]);
const { notifications } = useNotifications();
const { selectedTime: globalSelectedTime } = useSelector< const { selectedTime: globalSelectedTime } = useSelector<
AppState, AppState,
GlobalReducer GlobalReducer
@@ -50,6 +47,8 @@ function LiveLogsContainer(): JSX.Element {
handleCloseConnection, handleCloseConnection,
initialLoading, initialLoading,
isConnectionLoading, isConnectionLoading,
isConnectionError,
reconnectDueToError,
} = useEventSource(); } = useEventSource();
const compositeQuery = useGetCompositeQueryParam(); const compositeQuery = useGetCompositeQueryParam();
@@ -86,8 +85,8 @@ function LiveLogsContainer(): JSX.Element {
); );
const handleError = useCallback(() => { const handleError = useCallback(() => {
notifications.error({ message: 'Sorry, something went wrong' }); console.error('Sorry, something went wrong');
}, [notifications]); }, []);
useEventSourceEvent('message', handleGetLiveLogs); useEventSourceEvent('message', handleGetLiveLogs);
useEventSourceEvent('error', handleError); useEventSourceEvent('error', handleError);
@@ -153,6 +152,23 @@ function LiveLogsContainer(): JSX.Element {
handleStartNewConnection, handleStartNewConnection,
]); ]);
useEffect((): (() => void) | undefined => {
if (isConnectionError && reconnectDueToError && compositeQuery) {
// Small delay to prevent immediate reconnection attempts
const reconnectTimer = setTimeout(() => {
handleStartNewConnection(compositeQuery);
}, 1000);
return (): void => clearTimeout(reconnectTimer);
}
return undefined;
}, [
isConnectionError,
reconnectDueToError,
compositeQuery,
handleStartNewConnection,
]);
useEffect(() => { useEffect(() => {
const prefetchedList = queryLocationState?.listQueryPayload[0]?.list; const prefetchedList = queryLocationState?.listQueryPayload[0]?.list;

View File

@@ -1,9 +1,9 @@
import './LogsPanelComponent.styles.scss'; import './LogsPanelComponent.styles.scss';
import { Table } from 'antd';
import LogDetail from 'components/LogDetail'; import LogDetail from 'components/LogDetail';
import { VIEW_TYPES } from 'components/LogDetail/constants'; import { VIEW_TYPES } from 'components/LogDetail/constants';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { ResizeTable } from 'components/ResizeTable';
import { SOMETHING_WENT_WRONG } from 'constants/api'; import { SOMETHING_WENT_WRONG } from 'constants/api';
import { PANEL_TYPES } from 'constants/queryBuilder'; import { PANEL_TYPES } from 'constants/queryBuilder';
import Controls from 'container/Controls'; import Controls from 'container/Controls';
@@ -79,9 +79,14 @@ function LogsPanelComponent({
const { formatTimezoneAdjustedTimestamp } = useTimezone(); const { formatTimezoneAdjustedTimestamp } = useTimezone();
const columns = getLogPanelColumnsList( const columns = useMemo(
widget.selectedLogFields, () =>
formatTimezoneAdjustedTimestamp, getLogPanelColumnsList(
widget.selectedLogFields,
formatTimezoneAdjustedTimestamp,
),
// eslint-disable-next-line react-hooks/exhaustive-deps
[widget.selectedLogFields],
); );
const dataLength = const dataLength =
@@ -216,16 +221,18 @@ function LogsPanelComponent({
<div className="logs-table"> <div className="logs-table">
<div className="resize-table"> <div className="resize-table">
<OverlayScrollbar> <OverlayScrollbar>
<Table <ResizeTable
pagination={false} pagination={false}
tableLayout="fixed" tableLayout="fixed"
scroll={{ x: `calc(50vw - 10px)` }} scroll={{ x: `max-content` }}
sticky sticky
loading={queryResponse.isFetching} loading={queryResponse.isFetching}
style={tableStyles} style={tableStyles}
dataSource={flattenLogData} dataSource={flattenLogData}
columns={columns} columns={columns}
onRow={handleRow} onRow={handleRow}
widgetId={widget.id}
shouldPersistColumnWidths
/> />
</OverlayScrollbar> </OverlayScrollbar>
</div> </div>

View File

@@ -0,0 +1,16 @@
.inspect-metrics-modal {
.inspect-metrics-title {
display: flex;
align-items: center;
justify-content: flex-start;
gap: 6px;
.inspect-metrics-button {
display: flex;
align-items: center;
justify-content: center;
cursor: default;
color: var(--text-vanilla-500);
}
}
}

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