Compare commits

...

74 Commits

Author SHA1 Message Date
Prashant Shahi
2d6c5f43a1 chore(release): 📌 pin versions: SigNoz 0.31.1
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2023-10-13 13:19:26 +05:45
Rajat Dabade
9a433891f2 fix: the json parsing issue (#3739) 2023-10-13 11:35:50 +05:30
Prashant Shahi
3c63d66591 Merge pull request #3732 from SigNoz/release/v0.31.0
Release/v0.31.0
2023-10-12 20:40:51 +05:45
Prashant Shahi
5b69559762 Merge branch 'main' into release/v0.31.0 2023-10-12 19:46:51 +05:45
Prashant Shahi
d7a5c6d65b chore(release): 📌 pin versions: SigNoz 0.31.0, SigNoz OtelCollector 0.79.8
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2023-10-12 19:43:02 +05:45
Palash Gupta
1588d3a199 feat: limit is updated (#3730) 2023-10-12 18:43:08 +05:30
Palash Gupta
d5df9a1f7f fix: query key is updated (#3715) 2023-10-12 17:27:51 +05:30
Raj Kamal Singh
2be3d35952 feat: frontend: log pipelines preview (#3706)
* feat: add pipeline preview API

* chore: separate PipelineActions and ProcessorActions components

* feat: add pipeline preview action

* chore: extract useSampleLogs hook and move SampleLogs to filter preview components

* chore: extract SampleLogsResponseDisplay for reuse

* feat: bring together pipeline preview modal content

* chore: generalize SampleLogsResponse to LogsResponse

* feat: finish wiring up pipeline preview flow

* chore: separate response models for useSampleLogs and usePipelinePreview

* chore: require explicit action for simulation after changing logs sample search interval

* feat: error and empty state for pipeline simulation result

* chore: look for error in sample logs response data too

* chore: remove tests for deleted component & update snapshot for PipelineAction tests

* chore: minor cleanup

* chore: address feedback: move timestamp normalization out of api file

* chore: address feedback: use axios directly in pipeline preview API call

* chore: address feedback: use REACT_QUERY_KEY constant for useQuery key

* chore: minor cleanup

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
2023-10-12 17:11:23 +05:30
Rajat Dabade
7fa50070ce refactor: removed escape character from the string (#3726) 2023-10-12 12:21:04 +05:30
dependabot[bot]
2494b64ccd chore(deps): bump golang.org/x/net from 0.13.0 to 0.17.0 (#3719)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.13.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.13.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-12 06:10:45 +05:30
Palash Gupta
ca3283fcad fix: dashboard context is updated when we update (#3718) 2023-10-11 23:27:45 +05:30
Palash Gupta
a912731cc7 fix: limit for time series is updated (#3716) 2023-10-11 22:14:42 +05:30
Srikanth Chekuri
1a855582a7 chore: add billing api resources (#3704) 2023-10-11 18:14:08 +05:30
Nityananda Gohain
f3c00e1a57 Revert "feat: lowercase operators support in the where clause is updated (#3657)" (#3697)
This reverts commit 0e04b779a9.
2023-10-11 17:35:53 +05:30
Yunus M
0d3cbb1db2 feat: hoc to support markdown content with variable interpolation (#3667)
* feat: hoc to support markdown content with variable interpolation

* feat: add ingestion settings page

* feat: update ingestion settings page and java docs to use interpolation

* feat: integrate ingestion info API and update docs components to use ingestion info

* feat: address review comments and update <my-app> to <servive-name>
2023-10-11 15:33:24 +05:30
Srikanth Chekuri
2c96512a8a chore: do not allow deleting more than one panel on update request (#3703) 2023-10-10 13:07:20 +00:00
Raj Kamal Singh
a84a70df14 QS: logs pipelines preview http handler (#3701)
* feat: add logsparsingpipeline controller api for pipeline previews

* feat: add http handler and route for generating pipeline previews

* feat: use a response model for pipeline previews response
2023-10-10 14:09:55 +05:30
Vishal Sharma
dcea79cef3 feat: ingestion key management (#3699) 2023-10-09 21:06:01 +05:30
Palash Gupta
b12365ba07 fix: dependecy is updated for widget query range (#3698) 2023-10-09 20:18:43 +05:30
Raj Kamal Singh
718eb7b381 QS: logparsingpipeline previews (#3694)
* chore: rename model.GetLogsResponse -> model.SignozLog for use in both requests and responses

* feat: add test for simulating log pipelines processing

* feat: get pipeline preview tests passing

* chore: cleanup
2023-10-09 15:25:13 +05:30
Rajat Dabade
503417719c refactor: Added new props to GetMetricQueryRange to control Step from without global time range. (#3304)
* refactor: added new props to GetMetricQueryRange

* refactor: review comments

* chore: removed the unnecessary props in query-range payload

* chore: name updated

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
Co-authored-by: Palash Gupta <palashgdev@gmail.com>
2023-10-09 14:46:44 +05:30
Palash Gupta
e7a5eb7b22 feat: new dashboard page is updated (#3385)
* feat: dashboard widget page is refactored

* chore: key is updated

* chore: delete widget is updated

* chore: naming of the file is updated

* feat: dashboard changes are updated and selected dashboard and dashboardId is added

* chore: dashboard widget page is updated

* feat: setlayout is updated

* chore: selected dashboard is updated

* chore: dashboard is updated

* fix: feedback is updated

* chore: comments are resolved

* chore: empty widget id is updated

* fix: variables is updated

* chore: dashboard variable and name,description is now updated in hooks

* chore: build is fixed

* chore: loading experience is updated

* chore: title is updated

* fix: dashboard variables and other changes are updated

* feat: dashboard reducer is removed

* feat: widget header is updated

* feat: widget header is updated

* chore: dashboard is updated

* chore: feedback is updated

* fix: issues are fixed

* chore: delete is updated

* chore: warning message is updated

* chore: warning message is updated

* chore: widget graph component

* feat: dashboard condition is updated

* chore: getChartData is updated

* chore: widget details page is updated

* feat: tab sync is updated

* chore: layout is updated

* chore: labels is updated

* chore: message is updated

* chore: warining message is updated

---------

Co-authored-by: Rajat Dabade <rajat@signoz.io>
Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
2023-10-08 23:21:17 +05:30
Prashant Shahi
b14f800fee ci: 👷 pin Go v1.21 and bump up actions/* in GH build/push workflows (#3687)
* ci: 👷 pin Go v1.21 in GH build/push workflows

* chore: 💚 update actions/* to v4

---------

Signed-off-by: Prashant Shahi <prashant@signoz.io>
2023-10-08 22:56:58 +05:30
Raj Kamal Singh
9e91375632 Logs pipeline editor - filter preview (#3683)
* feat: get started with Logs Filter Preview

* chore: rename PipelineFilterPreview -> PipelineFilterSummary

* chore: initial styles for pipeline filter preview

* feat: wire up logs fetching for pipeline filter preview

* feat: show empty preview if filter is empty

* feat: get logs preview table display started

* feat: use simple div + style based display for logs preview

* feat: log preview item expand action

* feat: move preview below filter and make filter last i/p in pipeline form

* feat: add duration selector for logs filter preview

* feat: add matched logs count to pipeline filter preview

* chore: reorganize preview logs list into its own file

* chore: cleanup

* chore: revert type export from useGetQueryRange.ts

* chore: get all tests passing

* chore: address review comments: import cloneDeep directly

* chore: address review comments: avoid inline handler func, return JSX.Element | null

* chore: address review comments: move preview interval selector helper into its own folder

* chore: address feedback: fix cloneDeep import

* chore: address feedback: avoid inline handler and remove eslint supression
2023-10-08 14:49:16 +05:30
Prashant Shahi
d7d4000240 chore(query-service): 🔧 update workflows and build files as per optimization changes (#3686)
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2023-10-08 00:29:39 +05:30
Prashant Shahi
e12aef136a perf(query-service): 🔨 improve backend build time (#3658)
* perf(query-service): 🔨 improve backend build time

* chore(query-service): 🔧 address comments on image build time

---------

Signed-off-by: Prashant Shahi <prashant@signoz.io>
2023-10-07 21:08:53 +05:30
Palash Gupta
0e04b779a9 feat: lowercase operators support in the where clause is updated (#3657)
* feat: lowercase operators suuport in the where clause is updated

* feat: options is now updated

* chore: log message is updated

* chore: auto completed is updated

* chore: tagRegex is updated

* feat: update regex to math operators and text operators

* chore: operator is updated

* chore: options is updated

---------

Co-authored-by: Yunus A M <myounis.ar@live.com>
2023-10-06 17:32:17 +05:30
Rajat Dabade
587034f573 [Refactor]: graph manager to scss and fix the height issue (#3671)
* refactor: graph manager to scss and fix the height issue

* refactor: updated scss
2023-10-06 15:10:13 +05:30
Wayne Zhou
321cba2af5 docs: update the chinese readme to latest (#3670) 2023-10-06 09:30:52 +05:30
Nityananda Gohain
abed60bdfa fix: exists check for json filters added (#3675)
* fix: exists check for json filters added

* fix: comment updated
2023-10-06 09:26:37 +05:30
Yunus M
a306fb64cb feat: update analytics endpoints (#3674)
Co-authored-by: Palash Gupta <palashgdev@gmail.com>
2023-10-05 21:59:40 +05:30
Raj Kamal Singh
0ad5d67140 QS: Collector simulator (#3656)
* feat: get collectorsimulator started and add inmemoryreceiver

* feat: add collectorsimulator/inmemoryexporter

* feat: add collectorsimulator.SimulateLogsProcessing

* chore: clean up collector simulator code a little

* chore: update go.sum entries for cors

* chore: add collectorsimulator tests to make cmd

* chore: move to latest dependency version for collectorsimulator

* chore: revert to dependency versions matching signoz-otel-col

* chore: cleanup: reorganize collectorsimulator logic

* chore: some more cleanup

* chore: some more cleanup

* chore: some more cleanup

* chore: redo go.mod
2023-10-05 14:27:41 +05:30
Palash Gupta
11863040bb fix: alerts is now migrated to new alerts page (#3669) 2023-10-05 10:34:17 +05:30
Pranay Prateek
a67a3837c8 fix: frontend/package.json & frontend/yarn.lock to reduce vulnerabilities (#3655)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-POSTCSS-5926692

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-10-03 10:07:34 +05:30
Palash Gupta
81b10d126a feat: has and nhas filters is now enabled (#3567) 2023-10-02 05:04:04 +00:00
Nityananda Gohain
9f751688cc fix: limit issue fixed when using contains (#3649) 2023-09-29 18:20:40 +05:30
Prashant Shahi
3d0fbd0065 perf(frontend): 🔨 improve frontend build time (#3653)
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2023-09-29 17:10:14 +05:30
Rajat Dabade
05ea814c61 refactor: wrap tooltip text and remain file for tooltips (#3647) 2023-09-28 13:32:55 +05:30
Rajat Dabade
92ba46b2f5 fix: copy to clipboard without quotes (#3605) 2023-09-28 03:45:12 +00:00
Srikanth Chekuri
4bbe1ea614 Merge pull request #3641 from SigNoz/release/v0.30.0
Release/v0.30.0
2023-09-27 23:19:15 +05:30
Srikanth Chekuri
e3a251ef29 Merge branch 'main' into release/v0.30.0 2023-09-27 23:08:12 +05:30
Srikanth Chekuri
a4e0d9c7df chore: pin SigNoz version to v0.30.0 2023-09-27 23:04:02 +05:30
Srikanth Chekuri
4076cd9847 fix: alert eval for "="/"!=" combination with "at least once"/"all the times" (#3613) 2023-09-27 22:34:49 +05:30
Yunus M
e3f4fc2967 feat: fix use raw-loader instead of mdx-js/loader (#3640) 2023-09-27 14:26:51 +00:00
Palash Gupta
bccefc6a10 chore: error details stack trace height is updated (#3639)
* chore: error details stack trace height is updated

* chore: style is updated

---------

Co-authored-by: Rajat Dabade <rajat@signoz.io>
2023-09-27 17:50:10 +05:30
Palash Gupta
821471f4ab feat(query-builder): add limit, order by and having clause to formula (#3623)
* feat: query builder formula is updated

* feat: formula is updated for having and limit

* feat: orderBy is updated

* feat: formula is added

* chore: add query-service support for formula limit and order by

* feat: enable more filters is displayed when all data source is metrics

* chore: feedback is updated

* chore: feedback is updated

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
Co-authored-by: Rajat Dabade <rajat@signoz.io>
2023-09-27 17:34:47 +05:30
Ankit Anand
1e242b6d06 feat: updated infra docs (#3637)
* feat: updated infra docs

* feat: add selection for infra metrics types and render selected metrics type docs

* feat: qc updates

* feat: fix header alignment issue

---------

Co-authored-by: ankit01-oss <>
Co-authored-by: Yunus A M <myounis.ar@live.com>
2023-09-27 17:19:20 +05:30
Prashant Shahi
4ca5176836 ci(frontend-ee): 👷 add clarity project ID to env (#3635) 2023-09-27 08:24:55 +00:00
Yunus M
7f397d529b Onboarding Docs - Copy to clipboard (#3634)
* feat: enable copy-to-clipboard to onboarding docs snippets

* feat: remove commented code & <br></br> from md docs

* feat: remove react-copy-to-clipboard lib and fix type issues

* feat: markdown renderer - pre - remove any with reactnode
2023-09-27 13:20:48 +05:30
Yunus M
656f354fdc feat: ignore prettier formatting for build, coverage and md files (#3628) 2023-09-26 15:55:36 +00:00
Ankit Anand
4cc3ce224c docs: update onboarding docs (#3627)
Co-authored-by: ankit01-oss <>
2023-09-26 20:38:37 +05:30
Nityananda Gohain
a4a285c074 feat: add support for freehand json query (#3625)
* feat: freehand json search

* feat: support for freehand json query

* fix: minor updates

* fix: minor refactor
2023-09-26 20:10:39 +05:30
Yunus M
a8f8580606 feat: add account creation page events (#3619) 2023-09-26 13:21:59 +00:00
Yunus M
e24918044e feat: add clarity ms (#3620) 2023-09-26 13:06:53 +05:30
Palash Gupta
28d346eafb feat: default options is updated (#3607)
Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
2023-09-26 00:23:27 +05:30
Rajat Dabade
cbd2f4c643 [Fix]: select dropdown for onboarding flow (#3618)
* fix: select dropdown for onboarding flow

* refactor: dropdown scroll issue for widget header

* refactor: dropdown scroll issue for topnav

* refactor: fix the dropdown scroll issue

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
2023-09-25 19:39:10 +05:30
Palash Gupta
fcedc9e445 feat: severityText is added (#3606) 2023-09-25 12:17:26 +00:00
Yunus M
d2d3c4bb36 feat: make identity call if user is logs in for first time or if identity call was not registered (#3612) 2023-09-24 09:35:11 +00:00
Eng Zer Jun
dc4acc0730 refactor(query-service): remove redundant nil check (#3614)
From the Go specification [1]:

  "1. For a nil slice, the number of iterations is 0."
  "3. If the map is nil, the number of iterations is 0."

Therefore, an additional nil check for before the loop is unnecessary.

[1]: https://go.dev/ref/spec#For_range

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2023-09-24 14:42:17 +05:30
Srikanth Chekuri
043e5ca880 fix: skip first record only for rate metrics (#3609) 2023-09-22 15:43:21 +05:30
Rajat Dabade
5c437dd8f9 fix: the scroll issue and name issue in save view (#3604) 2023-09-21 19:49:43 +05:30
Palash Gupta
31b898b2c6 fix: active menu logic is updated (#3602) 2023-09-21 15:58:41 +05:30
Prashant Shahi
e186474414 Merge pull request #3600 from SigNoz/release/v0.29.3
Release/v0.29.3
2023-09-21 11:05:49 +05:30
Raj Kamal Singh
8bfb0b5088 QueryBuilder filters for log pipelines (#3587) 2023-09-21 05:11:48 +00:00
Prashant Shahi
045a31ac92 chore(release): 📌 pin versions: SigNoz 0.29.3
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2023-09-21 00:31:14 +05:30
Prashant Shahi
c9654a6b52 Merge branch 'main' into release/v0.29.3 2023-09-21 00:30:44 +05:30
Yunus M
30e0924bfb feat: onboarding flow - add analytics - update webpack config (#3599) 2023-09-20 23:37:59 +05:30
Yunus M
ccada08db5 feat: onboarding flow - add analytics - update webpack config (#3597) 2023-09-20 15:20:08 +00:00
Prashant Shahi
6654dd2672 ci(frontend): 👷 update CI job for frontend EE build (#3596)
Signed-off-by: Prashant Shahi <prashant@signoz.io>
2023-09-20 19:42:56 +05:30
Yunus M
4b0a7cc4d3 feat: onboarding flow - add analytics (#3594) 2023-09-20 16:00:51 +05:30
Rajat Dabade
04acc49154 refactor: polished the log UI (#3591) 2023-09-20 15:31:31 +05:30
Raj Kamal Singh
3db8a25eb9 Add support in query service for querybuilder filterset based log pipelines (#3560)
* chore: use v3.Filterset as pipeline filters in logparsing pipelines integration tests

* chore: get logparsing integration tests passing with filterset based pipeline

* chore: get all other breaking tests passing

* chore: move models.logparsingpipeline to logparsingpipeline.model

* chore: implement Valuer and Scanner interfaces for v3.FilterSet
2023-09-20 12:39:34 +05:30
Yunus M
8324d010ae fix: frontend/Dockerfile to reduce vulnerabilities (#3589)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-ALPINE317-LIBWEBP-5902239

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-09-20 11:32:17 +05:30
Srikanth Chekuri
f0022cd13f Merge pull request #3588 from SigNoz/release/v0.29.2
Release/v0.29.2
2023-09-19 21:54:16 +05:30
374 changed files with 9299 additions and 5833 deletions

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install dependencies
run: cd frontend && yarn install
- name: Run ESLint
@@ -31,9 +31,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Create .env file
run: echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
run: |
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
echo 'CLARITY_PROJECT_ID="${{ secrets.CLARITY_PROJECT_ID }}"' >> frontend/.env
- name: Install dependencies
run: cd frontend && yarn install
- name: Run ESLint
@@ -51,12 +54,12 @@ jobs:
build-query-service:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup golang
uses: actions/setup-go@v4
with:
go-version: "1.21"
- name: Checkout code
uses: actions/checkout@v3
- name: Run tests
shell: bash
run: |
@@ -69,12 +72,12 @@ jobs:
build-ee-query-service:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup golang
uses: actions/setup-go@v4
with:
go-version: "1.21"
- name: Checkout code
uses: actions/checkout@v3
- name: Build EE query-service image
shell: bash
run: |

View File

@@ -39,7 +39,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -7,7 +7,7 @@ jobs:
lint-commits:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: wagoid/commitlint-github-action@v5

View File

@@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout Codebase
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: signoz/gh-bot
- name: Use Node v16
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 16
- name: Setup Cache & Install Dependencies

View File

@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: 'Dependency Review'
with:
fail-on-severity: high

View File

@@ -13,7 +13,7 @@ jobs:
DOCKER_TAG: pull-${{ github.event.number }}
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Build query-service image
env:

View File

@@ -9,8 +9,8 @@ jobs:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "16.x"
- name: Install dependencies

View File

@@ -14,7 +14,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup golang
uses: actions/setup-go@v4
with:
go-version: "1.21"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
@@ -42,6 +46,11 @@ jobs:
else
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}-oss" >> $GITHUB_ENV
fi
- name: Install cross-compilation tools
run: |
set -ex
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
- name: Build and push docker image
run: make build-push-query-service
@@ -49,7 +58,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup golang
uses: actions/setup-go@v4
with:
go-version: "1.21"
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
@@ -77,6 +90,11 @@ jobs:
else
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV
fi
- name: Install cross-compilation tools
run: |
set -ex
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
- name: Build and push docker image
run: make build-push-ee-query-service
@@ -84,7 +102,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Install dependencies
working-directory: frontend
run: yarn install
@@ -128,9 +146,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Create .env file
run: echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
run: |
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
echo 'CLARITY_PROJECT_ID="${{ secrets.CLARITY_PROJECT_ID }}"' >> frontend/.env
- name: Install dependencies
working-directory: frontend
run: yarn install

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Sonar analysis

View File

@@ -26,6 +26,7 @@ jobs:
echo "GITHUB_SHA: ${GITHUB_SHA}"
export DOCKER_TAG="${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
docker system prune --force
docker pull signoz/signoz-otel-collector:main
cd ~/signoz

View File

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

View File

@@ -8,6 +8,7 @@ BUILD_HASH ?= $(shell git rev-parse --short HEAD)
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
DEV_LICENSE_SIGNOZ_IO ?= https://staging-license.signoz.io/api/v1
DEV_BUILD ?= "" # set to any non-empty value to enable dev build
# Internal variables or constants.
FRONTEND_DIRECTORY ?= frontend
@@ -15,15 +16,15 @@ QUERY_SERVICE_DIRECTORY ?= pkg/query-service
EE_QUERY_SERVICE_DIRECTORY ?= ee/query-service
STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup
SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup
LOCAL_GOOS ?= $(shell go env GOOS)
LOCAL_GOARCH ?= $(shell go env GOARCH)
GOOS ?= $(shell go env GOOS)
GOARCH ?= $(shell go env GOARCH)
GOPATH ?= $(shell go env GOPATH)
REPONAME ?= signoz
DOCKER_TAG ?= $(subst v,,$(BUILD_VERSION))
FRONTEND_DOCKER_IMAGE ?= frontend
QUERY_SERVICE_DOCKER_IMAGE ?= query-service
DEV_BUILD ?= ""
# Build-time Go variables
PACKAGE?=go.signoz.io/signoz
@@ -37,10 +38,22 @@ LD_FLAGS=-X ${buildHash}=${BUILD_HASH} -X ${buildTime}=${BUILD_TIME} -X ${buildV
DEV_LD_FLAGS=-X ${licenseSignozIo}=${DEV_LICENSE_SIGNOZ_IO}
all: build-push-frontend build-push-query-service
# Steps to build static files of frontend
build-frontend-static:
@echo "------------------"
@echo "--> Building frontend static files"
@echo "------------------"
@cd $(FRONTEND_DIRECTORY) && \
rm -rf build && \
CI=1 yarn install && \
yarn build && \
ls -l build
# Steps to build and push docker image of frontend
.PHONY: build-frontend-amd64 build-push-frontend
# Step to build docker image of frontend in amd64 (used in build pipeline)
build-frontend-amd64:
build-frontend-amd64: build-frontend-static
@echo "------------------"
@echo "--> Building frontend docker image for amd64"
@echo "------------------"
@@ -49,7 +62,7 @@ build-frontend-amd64:
--build-arg TARGETPLATFORM="linux/amd64" .
# Step to build and push docker image of frontend(used in push pipeline)
build-push-frontend:
build-push-frontend: build-frontend-static
@echo "------------------"
@echo "--> Building and pushing frontend docker image"
@echo "------------------"
@@ -57,24 +70,52 @@ build-push-frontend:
docker buildx build --file Dockerfile --progress plain --push --platform linux/arm64,linux/amd64 \
--tag $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) .
# Steps to build static binary of query service
.PHONY: build-query-service-static
build-query-service-static:
@echo "------------------"
@echo "--> Building query-service static binary"
@echo "------------------"
@if [ $(DEV_BUILD) != "" ]; then \
cd $(QUERY_SERVICE_DIRECTORY) && \
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS} ${DEV_LD_FLAGS}"; \
else \
cd $(QUERY_SERVICE_DIRECTORY) && \
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS}"; \
fi
.PHONY: build-query-service-static-amd64
build-query-service-static-amd64:
make GOARCH=amd64 build-query-service-static
.PHONY: build-query-service-static-arm64
build-query-service-static-arm64:
make CC=aarch64-linux-gnu-gcc GOARCH=arm64 build-query-service-static
# Steps to build static binary of query service for all platforms
.PHONY: build-query-service-static-all
build-query-service-static-all: build-query-service-static-amd64 build-query-service-static-arm64
# Steps to build and push docker image of query service
.PHONY: build-query-service-amd64 build-push-query-service
.PHONY: build-query-service-amd64 build-push-query-service
# Step to build docker image of query service in amd64 (used in build pipeline)
build-query-service-amd64:
build-query-service-amd64: build-query-service-static-amd64
@echo "------------------"
@echo "--> Building query-service docker image for amd64"
@echo "------------------"
@docker build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile \
-t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" .
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
--build-arg TARGETPLATFORM="linux/amd64" .
# Step to build and push docker image of query in amd64 and arm64 (used in push pipeline)
build-push-query-service:
build-push-query-service: build-query-service-static-all
@echo "------------------"
@echo "--> Building and pushing query-service docker image"
@echo "------------------"
@docker buildx build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile --progress plain \
--push --platform linux/arm64,linux/amd64 --build-arg LD_FLAGS="$(LD_FLAGS)" \
--push --platform linux/arm64,linux/amd64 \
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
# Step to build EE docker image of query service in amd64 (used in build pipeline)
@@ -82,24 +123,14 @@ build-ee-query-service-amd64:
@echo "------------------"
@echo "--> Building query-service docker image for amd64"
@echo "------------------"
@if [ $(DEV_BUILD) != "" ]; then \
docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
-t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="${LD_FLAGS} ${DEV_LD_FLAGS}" .; \
else \
docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
-t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" .; \
fi
make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-query-service-amd64
# Step to build and push EE docker image of query in amd64 and arm64 (used in push pipeline)
build-push-ee-query-service:
@echo "------------------"
@echo "--> Building and pushing query-service docker image"
@echo "------------------"
@docker buildx build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
--progress plain --push --platform linux/arm64,linux/amd64 \
--build-arg LD_FLAGS="$(LD_FLAGS)" --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-push-query-service
dev-setup:
mkdir -p /var/lib/signoz
@@ -110,7 +141,7 @@ dev-setup:
@echo "------------------"
run-local:
@LOCAL_GOOS=$(LOCAL_GOOS) LOCAL_GOARCH=$(LOCAL_GOARCH) docker-compose -f \
@docker-compose -f \
$(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-local.yaml \
up --build -d
@@ -151,4 +182,6 @@ test:
go test ./pkg/query-service/app/querier/...
go test ./pkg/query-service/converter/...
go test ./pkg/query-service/formatter/...
go test ./pkg/query-service/tests/integration/...
go test ./pkg/query-service/tests/integration/...
go test ./pkg/query-service/rules/...
go test ./pkg/query-service/collectorsimulator/...

View File

@@ -1,170 +1,225 @@
<p align="center">
<img src="https://res.cloudinary.com/dcv3epinx/image/upload/v1618904450/signoz-images/LogoGithub_sigfbu.svg" alt="SigNoz-logo" width="240" />
<img src="https://res.cloudinary.com/dcv3epinx/image/upload/v1618904450/signoz-images/LogoGithub_sigfbu.svg" alt="SigNoz-logo" width="240" />
<p align="center">你的应用,并可排查已部署应用的问题,这是一个开源的可替代DataDog、NewRelic方案</p>
<p align="center">你的应用,并可排查已部署应用的问题,这是一个可替代 DataDog、NewRelic 的开源方案</p>
</p>
<p align="center">
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/frontend?label=Downloads"> </a>
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/query-service?label=Docker Downloads"> </a>
<img alt="GitHub issues" src="https://img.shields.io/github/issues/signoz/signoz"> </a>
<a href="https://twitter.com/intent/tweet?text=Monitor%20your%20applications%20and%20troubleshoot%20problems%20with%20SigNoz,%20an%20open-source%20alternative%20to%20DataDog,%20NewRelic.&url=https://signoz.io/&via=SigNozHQ&hashtags=opensource,signoz,observability">
<img alt="tweet" src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social"> </a>
</p>
##
<h3 align="center">
<a href="https://signoz.io/docs"><b>文档</b></a>
<a href="https://github.com/SigNoz/signoz/blob/develop/README.zh-cn.md"><b>中文ReadMe</b></a>
<a href="https://github.com/SigNoz/signoz/blob/develop/README.de-de.md"><b>德文ReadMe</b></a>
<a href="https://github.com/SigNoz/signoz/blob/develop/README.pt-br.md"><b>葡萄牙语ReadMe</b></a>
<a href="https://signoz.io/slack"><b>Slack 社区</b></a>
<a href="https://twitter.com/SigNozHq"><b>Twitter</b></a>
</h3>
SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNoz使用分布式追踪来增加软件技术栈的可见性。
##
👉 你能看到一些性能指标服务、外部api调用、每个终端(endpoint)的p99延迟和错误率。
SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可以使用 SigNoz 实现如下能力:
👉 通过准确的追踪来确定是什么引起了问题,并且可以看到每个独立请求的帧图(framegraph),这样你就能找到根本原因
👉 在同一块面板上,可视化 Metrics, Traces 和 Logs 内容
👉 聚合trace数据来获得业务相关指标
👉 你可以关注服务的 p99 延迟和错误率, 包括外部 API 调用和个别的端点
![screenzy-1644432902955](https://user-images.githubusercontent.com/504541/153270713-1b2156e6-ec03-42de-975b-3c02b8ec1836.png)
<br />
![screenzy-1644432986784](https://user-images.githubusercontent.com/504541/153270725-0efb73b3-06ed-4207-bf13-9b7e2e17c4b8.png)
<br />
![screenzy-1647005040573](https://user-images.githubusercontent.com/504541/157875938-a3d57904-ea6d-4278-b929-bd1408d7f94c.png)
👉 你可以找到问题的根因,通过提取相关问题的 traces 日志、单独查看请求 traces 的火焰图详情。
👉 执行 trace 数据聚合,以获取业务相关的 metrics
👉 对日志过滤和查询,通过日志的属性建立看板和告警
👉 通过 PythonjavaRuby 和 Javascript 自动记录异常
👉 轻松的自定义查询和设置告警
### 应用 Metrics 展示
![application_metrics](https://user-images.githubusercontent.com/83692067/226637410-900dbc5e-6705-4b11-a10c-bd0faeb2a92f.png)
### 分布式追踪
<img width="2068" alt="distributed_tracing_2 2" src="https://user-images.githubusercontent.com/83692067/226536447-bae58321-6a22-4ed3-af80-e3e964cb3489.png">
<img width="2068" alt="distributed_tracing_1" src="https://user-images.githubusercontent.com/83692067/226536462-939745b6-4f9d-45a6-8016-814837e7f7b4.png">
### 日志管理
<img width="2068" alt="logs_management" src="https://user-images.githubusercontent.com/83692067/226536482-b8a5c4af-b69c-43d5-969c-338bd5eaf1a5.png">
### 基础设施监控
<img width="2068" alt="infrastructure_monitoring" src="https://user-images.githubusercontent.com/83692067/226536496-f38c4dbf-e03c-4158-8be0-32d4a61158c7.png">
### 异常监控
![exceptions_light](https://user-images.githubusercontent.com/83692067/226637967-4188d024-3ac9-4799-be95-f5ea9c45436f.png)
### 告警
<img width="2068" alt="alerts_management" src="https://user-images.githubusercontent.com/83692067/226536548-2c81e2e8-c12d-47e8-bad7-c6be79055def.png">
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributing.svg" width="50px" />
## 加入我们 Slack 社区
## 加入我们的Slack社区
来[Slack](https://signoz.io/slack) 跟我们打声招呼👋
来 [Slack](https://signoz.io/slack) 和我们打招呼吧 👋
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Features.svg" width="50px" />
## 特性:
## 功能:
- 为 metrics, traces and logs 制定统一的 UI。 无需切换 Prometheus 到 Jaeger 去查找问题,也无需使用想 Elastic 这样的日志工具分开你的 metrics 和 traces
- 应用概览指标(metrics)如RPS, p50/p90/p99延迟率分位值错误率等。
- 应用中最慢的终端(endpoint)
- 查看特定请求的trace数据来分析下游服务问题、慢数据库查询问题 及调用第三方服务如支付网关的问题
- 通过服务名称、操作、延迟、错误、标签来过滤traces。
- 聚合trace数据(events/spans)来得到业务相关指标。比如,你可以通过过滤条件`customer_type: gold` or `deployment_version: v2` or `external_call: paypal` 来获取指定业务的错误率和p99延迟
- 为metrics和trace提供统一的UI。排查问题不需要在Prometheus和Jaeger之间切换。
- 默认统计应用的 metrics 数据,像 RPS (每秒请求数) 50th/90th/99th 的分位数延迟数据,还有相关的错误率
- 找到应用中最慢的端点
- 查看准确的请求跟踪数据,找到下游服务的问题了,比如 DB 慢查询,或者调用第三方的支付网关等
- 通过 服务名、操作方式、延迟、错误、标签/注释 过滤 traces 数据
- 通过聚合 trace 数据而获得业务相关的 metrics。 比如你可以通过 `customer_type: gold` 或者 `deployment_version: v2` 或者 `external_call: paypal` 获取错误率和 P99 延迟数据
- 原生支持 OpenTelemetry 日志,高级日志查询,自动收集 k8s 相关日志
- 快如闪电的日志分析 ([Logs Perf. Benchmark](https://signoz.io/blog/logs-performance-benchmark/))
- 可视化点到点的基础设施性能,提取有所有类型机器的 metrics 数据
- 轻易自定义告警查询
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/WhatsCool.svg" width="50px" />
## 为什么使用 SigNoz?
## 为何选择SigNoz
作为开发者, 我们发现 SaaS 厂商对一些大家想要的小功能都是闭源的,这种行为真的让人有点恼火。 闭源厂商还会在月底给你一张没有明细的巨额账单。
作为开发人员我们发现依赖闭源的SaaS厂商提供的每个小功能有些麻烦闭源厂商通常会给你一份巨额月付账单但不提供足够的透明度你不知道你为哪些功能付费
我们想做一个自托管并且可开源的工具,像 DataDog 和 NewRelic 那样, 为那些担心数据隐私和安全的公司提供第三方服务
我们想做一个自服务的开源版本的工具类似于DataDog和NewRelic用于那些对客户数据流入第三方有隐私和安全担忧的厂商。
作为开源的项目,你完全可以自己掌控你的配置、样本和更新。你同样可以基于 SigNoz 拓展特定的业务模块。
开源也让你对配置、采样和正常运行时间有完整的控制你可以在SigNoz基础上构建模块来满足特定的商业需求。
### 支持的编程语言:
### 语言支持
我们支持[OpenTelemetry](https://opentelemetry.io)库你可以使用它来装备应用。也就是说SigNoz支持任何支持OpenTelemetry库的框架和语言。 主要支持语言包括:
我们支持 [OpenTelemetry](https://opentelemetry.io)。作为一个观测你应用的库文件。所以任何 OpenTelemetry 支持的框架和语言,对于 SigNoz 也同样支持。 一些主要支持的语言如下:
- Java
- Python
- NodeJS
- Go
- PHP
- .NET
- Ruby
- Elixir
- Rust
你可以在这个文档里找到完整的语言列表 - https://opentelemetry.io/docs/
你可以在这里找到全部支持的语言列表 - https://opentelemetry.io/docs/
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Philosophy.svg" width="50px" />
## 让我们开始吧
## 入门
### 使用 Docker 部署
请一步步跟随 [这里](https://signoz.io/docs/install/docker/) 通过 docker 来安装。
### 使用Docker部署
请按照[这里](https://signoz.io/docs/install/docker/)列出的步骤使用Docker来安装
如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/install/troubleshooting/)会对你有帮助。
这个 [排障说明书](https://signoz.io/docs/install/troubleshooting/) 可以帮助你解决碰到的问题。
<p>&nbsp </p>
### 使用 Helm 在 Kubernetes 部署
### 使用Helm在Kubernetes上部署
请跟着[这里](https://signoz.io/docs/deployment/helm_chart)的步骤使用helm charts安装
请一步步跟随 [这里](https://signoz.io/docs/deployment/helm_chart) 通过 helm 来安装
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/UseSigNoz.svg" width="50px" />
## 与其他方案的比较
## 比较相似的工具
### SigNoz vs Prometheus
如果你只是需要监控指标(metrics)那Prometheus是不错的如果你无缝的metricstraces之间切换,那目前把Prometheus & Jaeger串起来的体验并不好
Prometheus 是一个针对 metrics 监控的强大工具。但是如果你无缝的切换 metricstraces 查询,你当前大概率需要在 Prometheus Jaeger 之间切换
我们的目标是metricstraces提供统一的UI - 类似于Datadog这样的Saas厂提供的方案。并且能够对trace进行过滤和聚合这是目前Jaeger缺失的功能。
我们的目标是提供一个客户观测 metricstraces 整合的 UI。就像 SaaS 供应商 DataDog,它提供很多 jaeger 缺失的功能,比如针对 traces 过滤功能和聚合功能。
<p>&nbsp </p>
### SigNoz vs Jaeger
Jaeger只做分布式追踪(distributed tracing)SigNoz则支持metrics,traces,logs ,即可视化的三大支柱
Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metrics, traceslogs 所有的观测
并且SigNoz有一些Jaeger没有的高级功能
而且, SigNoz 相较于 Jaeger 拥有更对的高级功能:
- Jaegar UI无法在traces或过滤的traces上展示metrics。
- Jaeger不能对过滤的traces做聚合操作。例如拥有tag为customer_type='premium'的所有请求的p99延迟。而这个功能在SigNoz这儿是很容易实现。
- Jaegar UI 不能提供任何基于 traces 的 metrics 查询和过滤。
- Jaeger 不能针对过滤的 traces 做聚合。 比如, p99 延迟的请求有个标签是 customer_type='premium'。 而这些在 SigNoz 可以轻松做到。
<p>&nbsp </p>
### SigNoz vs Elastic
- SigNoz 的日志管理是基于 ClickHouse 实现的,可以使日志的聚合更加高效,因为它是基于 OLAP 的数据仓储。
- 与 Elastic 相比,可以节省 50% 的资源成本
我们已经公布了 Elastic 和 SigNoz 的性能对比。 请点击 [这里](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
<p>&nbsp </p>
### SigNoz vs Loki
- SigNoz 支持大容量高基数的聚合,但是 loki 是不支持的。
- SigNoz 支持索引的高基数查询,并且对索引没有数量限制,而 Loki 会在添加部分索引后到达最大上限。
- 相较于 SigNozLoki 在搜索大量数据下既困难又缓慢。
我们已经发布了基准测试对比 Loki 和 SigNoz 性能。请点击 [这里](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributors.svg" width="50px" />
## 贡献
我们 ❤️ 你的贡献,无论大小。 请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 再开始给 SigNoz 做贡献。
我们 ❤️ 任何贡献无论大小。 请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 然后开始给Signoz做贡献
如果你不知道如何开始? 只需要在 [slack 社区](https://signoz.io/slack) 通过 `#contributing` 频道联系我们
还不清楚怎么开始? 只需在[slack社区](https://signoz.io/slack)的`#contributing`频道里ping我们。
### 项目维护人员
### Project maintainers
#### Backend
#### 后端
- [Ankit Nayan](https://github.com/ankitnayan)
- [Nityananda Gohain](https://github.com/nityanandagohain)
- [Srikanth Chekuri](https://github.com/srikanthccv)
- [Vishal Sharma](https://github.com/makeavish)
#### Frontend
#### 前端
- [Palash Gupta](https://github.com/palashgdev)
#### DevOps
#### 运维开发
- [Prashant Shahi](https://github.com/prashant-shahi)
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/DevelopingLocally.svg" width="50px" />
## 文档
文档在这里:https://signoz.io/docs/. 如果你觉得有任何不清楚或者有文档缺失请在Github里发一个问题并使用标签 `documentation` 或者在社区stack频道里告诉我们
你可以通过 https://signoz.io/docs/ 找到相关文档。如果你需要阐述问题或者发现一些确实的事件, 通过标签 `documentation` 提交 Github 问题。或者通过 slack 社区频道
<br /><br />
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributing.svg" width="50px" />
## 社区
加入[slack community](https://signoz.io/slack)了解更多关于分布式踪、可观察性(observability),以及SigNoz。同时与其他用户和贡献者一起交流。
加入 [slack 社区](https://signoz.io/slack)了解更多关于分布式踪、可观测性系统 。或者与 SigNoz 其他用户和贡献者交流。
如果你有任何想法、问题或者反馈,请在[Github Discussions](https://github.com/SigNoz/signoz/discussions)分享给我们
如果你有任何想法、问题或者任何反馈, 请通过 [Github Discussions](https://github.com/SigNoz/signoz/discussions) 分享。
最后,感谢我们这些优秀的贡献者们。
不管怎么样,感谢这个项目的所有贡献者!
<a href="https://github.com/signoz/signoz/graphs/contributors">
<img src="https://contrib.rocks/image?repo=signoz/signoz" />
</a>

View File

@@ -144,7 +144,7 @@ services:
condition: on-failure
query-service:
image: signoz/query-service:0.29.2
image: signoz/query-service:0.31.1
command:
[
"-config=/root/config/prometheus.yml",
@@ -184,7 +184,7 @@ services:
<<: *clickhouse-depend
frontend:
image: signoz/frontend:0.29.2
image: signoz/frontend:0.31.1
deploy:
restart_policy:
condition: on-failure
@@ -197,7 +197,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
image: signoz/signoz-otel-collector:0.79.7
image: signoz/signoz-otel-collector:0.79.8
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -230,7 +230,7 @@ services:
<<: *clickhouse-depend
otel-collector-metrics:
image: signoz/signoz-otel-collector:0.79.7
image: signoz/signoz-otel-collector:0.79.8
command:
[
"--config=/etc/otel-collector-metrics-config.yaml",

View File

@@ -48,7 +48,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
otel-collector:
container_name: signoz-otel-collector
image: signoz/signoz-otel-collector:0.79.7
image: signoz/signoz-otel-collector:0.79.8
command:
[
"--config=/etc/otel-collector-config.yaml",
@@ -78,7 +78,7 @@ services:
otel-collector-metrics:
container_name: signoz-otel-collector-metrics
image: signoz/signoz-otel-collector:0.79.7
image: signoz/signoz-otel-collector:0.79.8
command:
[
"--config=/etc/otel-collector-metrics-config.yaml",

View File

@@ -8,7 +8,7 @@ services:
dockerfile: "./Dockerfile"
args:
LDFLAGS: ""
TARGETPLATFORM: "${LOCAL_GOOS}/${LOCAL_GOARCH}"
TARGETPLATFORM: "${GOOS}/${GOARCH}"
container_name: signoz-query-service
environment:
- ClickHouseUrl=tcp://clickhouse:9000
@@ -52,8 +52,8 @@ services:
context: "../../../frontend"
dockerfile: "./Dockerfile"
args:
TARGETOS: "${LOCAL_GOOS}"
TARGETPLATFORM: "${LOCAL_GOARCH}"
TARGETOS: "${GOOS}"
TARGETPLATFORM: "${GOARCH}"
container_name: signoz-frontend
environment:
- FRONTEND_API_ENDPOINT=http://query-service:8080

View File

@@ -162,7 +162,7 @@ services:
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.29.2}
image: signoz/query-service:${DOCKER_TAG:-0.31.1}
container_name: signoz-query-service
command:
[
@@ -201,7 +201,7 @@ services:
<<: *clickhouse-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.29.2}
image: signoz/frontend:${DOCKER_TAG:-0.31.1}
container_name: signoz-frontend
restart: on-failure
depends_on:
@@ -213,7 +213,7 @@ services:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.7}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.8}
container_name: signoz-otel-collector
command:
[
@@ -244,7 +244,7 @@ services:
<<: *clickhouse-depend
otel-collector-metrics:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.7}
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.8}
container_name: signoz-otel-collector-metrics
command:
[

View File

@@ -1,40 +1,20 @@
FROM golang:1.21-bookworm AS builder
# LD_FLAGS is passed as argument from Makefile. It will be empty, if no argument passed
ARG LD_FLAGS
ARG TARGETPLATFORM
ENV CGO_ENABLED=1
ENV GOPATH=/go
RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2)
# Prepare and enter src directory
WORKDIR /go/src/github.com/signoz/signoz
# Add the sources and proceed with build
ADD . .
RUN cd ee/query-service \
&& go build -tags timetzdata -a -o ./bin/query-service \
-ldflags "-linkmode external -extldflags '-static' -s -w $LD_FLAGS" \
&& chmod +x ./bin/query-service
# use a minimal alpine image
FROM alpine:3.16.7
FROM alpine:3.17
# Add Maintainer Info
LABEL maintainer="signoz"
# define arguments that can be passed during build time
ARG TARGETOS TARGETARCH
# add ca-certificates in case you need them
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
# set working directory
WORKDIR /root
# copy the binary from builder
COPY --from=builder /go/src/github.com/signoz/signoz/ee/query-service/bin/query-service .
# copy the query-service binary
COPY ee/query-service/bin/query-service-${TARGETOS}-${TARGETARCH} /root/query-service
# copy prometheus YAML config
COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml
@@ -45,7 +25,6 @@ RUN chmod 755 /root /root/query-service
# run the binary
ENTRYPOINT ["./query-service"]
CMD ["-config", "../config/prometheus.yml"]
# CMD ["./query-service -config /root/config/prometheus.yml"]
CMD ["-config", "/root/config/prometheus.yml"]
EXPOSE 8080

View File

@@ -8,6 +8,7 @@ import (
"go.signoz.io/signoz/ee/query-service/dao"
"go.signoz.io/signoz/ee/query-service/interfaces"
"go.signoz.io/signoz/ee/query-service/license"
"go.signoz.io/signoz/ee/query-service/usage"
baseapp "go.signoz.io/signoz/pkg/query-service/app"
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
"go.signoz.io/signoz/pkg/query-service/cache"
@@ -27,6 +28,7 @@ type APIHandlerOptions struct {
DialTimeout time.Duration
AppDao dao.ModelDao
RulesManager *rules.Manager
UsageManager *usage.Manager
FeatureFlags baseint.FeatureLookup
LicenseManager *license.Manager
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
@@ -82,6 +84,10 @@ func (ah *APIHandler) LM() *license.Manager {
return ah.opts.LicenseManager
}
func (ah *APIHandler) UM() *usage.Manager {
return ah.opts.UsageManager
}
func (ah *APIHandler) AppDao() dao.ModelDao {
return ah.opts.AppDao
}
@@ -150,6 +156,13 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.getPATs)).Methods(http.MethodGet)
router.HandleFunc("/api/v1/pat/{id}", am.OpenAccess(ah.deletePAT)).Methods(http.MethodDelete)
router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.checkout)).Methods(http.MethodPost)
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
router.HandleFunc("/api/v2/licenses",
am.ViewAccess(ah.listLicensesV2)).
Methods(http.MethodGet)
ah.APIHandler.RegisterRoutes(router, am)
}

View File

@@ -4,10 +4,44 @@ import (
"context"
"encoding/json"
"fmt"
"go.signoz.io/signoz/ee/query-service/model"
"io"
"net/http"
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
"go.uber.org/zap"
)
type tierBreakdown struct {
UnitPrice float64 `json:"unitPrice"`
Quantity int64 `json:"quantity"`
TierStart int64 `json:"tierStart"`
TierEnd int64 `json:"tierEnd"`
TierCost float64 `json:"tierCost"`
}
type usageResponse struct {
Type string `json:"type"`
Unit string `json:"unit"`
Tiers []tierBreakdown `json:"tiers"`
}
type details struct {
Total float64 `json:"total"`
Breakdown []usageResponse `json:"breakdown"`
BaseFee float64 `json:"baseFee"`
}
type billingDetails struct {
Status string `json:"status"`
Data struct {
BillingPeriodStart int64 `json:"billingPeriodStart"`
BillingPeriodEnd int64 `json:"billingPeriodEnd"`
Details details `json:"details"`
Discount float64 `json:"discount"`
} `json:"data"`
}
func (ah *APIHandler) listLicenses(w http.ResponseWriter, r *http.Request) {
licenses, apiError := ah.LM().GetLicenses(context.Background())
if apiError != nil {
@@ -38,3 +72,150 @@ func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
ah.Respond(w, license)
}
func (ah *APIHandler) checkout(w http.ResponseWriter, r *http.Request) {
type checkoutResponse struct {
Status string `json:"status"`
Data struct {
RedirectURL string `json:"redirectURL"`
} `json:"data"`
}
hClient := &http.Client{}
req, err := http.NewRequest("POST", constants.LicenseSignozIo+"/checkout", r.Body)
if err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
licenseResp, err := hClient.Do(req)
if err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
// decode response body
var resp checkoutResponse
if err := json.NewDecoder(licenseResp.Body).Decode(&resp); err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
ah.Respond(w, resp.Data)
}
func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
licenseKey := r.URL.Query().Get("licenseKey")
if licenseKey == "" {
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
return
}
billingURL := fmt.Sprintf("%s/usage?licenseKey=%s", constants.LicenseSignozIo, licenseKey)
hClient := &http.Client{}
req, err := http.NewRequest("GET", billingURL, nil)
if err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
billingResp, err := hClient.Do(req)
if err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
// decode response body
var billingResponse billingDetails
if err := json.NewDecoder(billingResp.Body).Decode(&billingResponse); err != nil {
RespondError(w, model.InternalError(err), nil)
return
}
// TODO(srikanthccv):Fetch the current day usage and add it to the response
ah.Respond(w, billingResponse.Data)
}
func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) {
licenses, apiError := ah.LM().GetLicenses(context.Background())
if apiError != nil {
RespondError(w, apiError, nil)
}
resp := model.Licenses{
TrialStart: -1,
TrialEnd: -1,
OnTrial: false,
WorkSpaceBlock: false,
Licenses: licenses,
}
var currentActiveLicenseKey string
for _, license := range licenses {
if license.IsCurrent {
currentActiveLicenseKey = license.Key
}
}
// For the case when no license is applied i.e community edition
// There will be no trial details or license details
if currentActiveLicenseKey == "" {
ah.Respond(w, resp)
return
}
// Fetch trial details
hClient := &http.Client{}
url := fmt.Sprintf("%s/trial?licenseKey=%s", constants.LicenseSignozIo, currentActiveLicenseKey)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
zap.S().Error("Error while creating request for trial details", err)
// If there is an error in fetching trial details, we will still return the license details
// to avoid blocking the UI
ah.Respond(w, resp)
return
}
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
trialResp, err := hClient.Do(req)
if err != nil {
zap.S().Error("Error while fetching trial details", err)
// If there is an error in fetching trial details, we will still return the license details
// to avoid incorrectly blocking the UI
ah.Respond(w, resp)
return
}
defer trialResp.Body.Close()
trialRespBody, err := io.ReadAll(trialResp.Body)
if err != nil || trialResp.StatusCode != http.StatusOK {
zap.S().Error("Error while fetching trial details", err)
// If there is an error in fetching trial details, we will still return the license details
// to avoid incorrectly blocking the UI
ah.Respond(w, resp)
return
}
// decode response body
var trialRespData model.SubscriptionServerResp
if err := json.Unmarshal(trialRespBody, &trialRespData); err != nil {
zap.S().Error("Error while decoding trial details", err)
// If there is an error in fetching trial details, we will still return the license details
// to avoid incorrectly blocking the UI
ah.Respond(w, resp)
return
}
resp.TrialStart = trialRespData.Data.TrialStart
resp.TrialEnd = trialRespData.Data.TrialEnd
resp.OnTrial = trialRespData.Data.OnTrial
resp.WorkSpaceBlock = trialRespData.Data.WorkSpaceBlock
ah.Respond(w, resp)
}

View File

@@ -217,6 +217,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
DialTimeout: serverOptions.DialTimeout,
AppDao: modelDao,
RulesManager: rm,
UsageManager: usageManager,
FeatureFlags: lm,
LicenseManager: lm,
LogsParsingPipelineController: logParsingPipelineController,

View File

@@ -9,6 +9,7 @@ const (
)
var LicenseSignozIo = "https://license.signoz.io/api/v1"
var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
var SpanLimitStr = GetOrDefaultEnv("SPAN_LIMIT", "5000")

View File

@@ -89,3 +89,16 @@ func (l *License) ParseFeatures() {
l.FeatureSet = BasicPlan
}
}
type Licenses struct {
TrialStart int64 `json:"trialStart"`
TrialEnd int64 `json:"trialEnd"`
OnTrial bool `json:"onTrial"`
WorkSpaceBlock bool `json:"workSpaceBlock"`
Licenses []License `json:"licenses"`
}
type SubscriptionServerResp struct {
Status string `json:"status"`
Data Licenses `json:"data"`
}

View File

@@ -1,4 +1,3 @@
node_modules
.vscode
build
.git

6
frontend/.prettierignore Normal file
View File

@@ -0,0 +1,6 @@
# Ignore artifacts:
build
coverage
# Ignore all MD files:
**/*.md

View File

@@ -1,38 +1,17 @@
# Builder stage
FROM node:16.15.0 as builder
FROM nginx:1.25.2-alpine
# Add Maintainer Info
LABEL maintainer="signoz"
ARG TARGETOS=linux
ARG TARGETARCH
# Set working directory
WORKDIR /frontend
# Copy the package.json and .yarnrc files prior to install dependencies
COPY package.json ./
# Copy lock file
COPY yarn.lock ./
COPY .yarnrc ./
# Install the dependencies and make the folder
RUN CI=1 yarn install
COPY . .
# Build the project and copy the files
RUN yarn build
FROM nginx:1.24.0-alpine
COPY conf/default.conf /etc/nginx/conf.d/default.conf
# Remove default nginx index page
RUN rm -rf /usr/share/nginx/html/*
# Copy from the stahg 1
COPY --from=builder /frontend/build /usr/share/nginx/html
# Copy custom nginx config and static files
COPY conf/default.conf /etc/nginx/conf.d/default.conf
COPY build /usr/share/nginx/html
EXPOSE 3301

View File

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

View File

@@ -52,7 +52,7 @@
"color": "^4.2.1",
"color-alpha": "1.1.3",
"cross-env": "^7.0.3",
"css-loader": "4.3.0",
"css-loader": "5.0.0",
"css-minimizer-webpack-plugin": "5.0.1",
"dayjs": "^1.10.7",
"dompurify": "3.0.0",
@@ -84,9 +84,11 @@
"react-helmet-async": "1.3.0",
"react-i18next": "^11.16.1",
"react-intersection-observer": "9.4.1",
"react-markdown": "8.0.7",
"react-query": "^3.34.19",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",
"react-syntax-highlighter": "15.5.0",
"react-use": "^17.3.2",
"react-virtuoso": "4.0.3",
"redux": "^4.0.5",
@@ -150,6 +152,7 @@
"@types/react-redux": "^7.1.11",
"@types/react-resizable": "3.0.3",
"@types/react-router-dom": "^5.1.6",
"@types/react-syntax-highlighter": "15.5.7",
"@types/styled-components": "^5.1.4",
"@types/uuid": "^8.3.1",
"@types/webpack": "^5.28.0",
@@ -183,6 +186,7 @@
"lint-staged": "^12.5.0",
"portfinder-sync": "^0.0.2",
"prettier": "2.2.1",
"raw-loader": "4.0.2",
"react-hooks-testing-library": "0.6.0",
"react-hot-loader": "^4.13.0",
"react-resizable": "3.0.4",

View File

@@ -13,5 +13,12 @@
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
"error_loading_json": "Error loading JSON file",
"empty_json_not_allowed": "Empty JSON is not allowed",
"new_dashboard_title": "Sample Title"
"new_dashboard_title": "Sample Title",
"layout_saved_successfully": "Layout saved successfully",
"add_panel": "Add Panel",
"save_layout": "Save Layout",
"variable_updated_successfully": "Variable updated successfully",
"error_while_updating_variable": "Error while updating variable",
"dashboard_has_been_updated": "Dashboard has been updated",
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?"
}

View File

@@ -1,13 +1,14 @@
{
"general": "General",
"alert_channels": "Alert Channels",
"organization_settings": "Organization Settings",
"my_settings": "My Settings",
"overview_metrics": "Overview Metrics",
"dbcall_metrics": "Database Calls",
"external_metrics": "External Calls",
"pipeline": "Pipeline",
"pipelines": "Pipelines",
"archives": "Archives",
"logs_to_metrics": "Logs To Metrics"
}
{
"general": "General",
"alert_channels": "Alert Channels",
"organization_settings": "Organization Settings",
"ingestion_settings": "Ingestion Settings",
"my_settings": "My Settings",
"overview_metrics": "Overview Metrics",
"dbcall_metrics": "Database Calls",
"external_metrics": "External Calls",
"pipeline": "Pipeline",
"pipelines": "Pipelines",
"archives": "Archives",
"logs_to_metrics": "Logs To Metrics"
}

View File

@@ -1,37 +1,38 @@
{
"SIGN_UP": "SigNoz | Sign Up",
"LOGIN": "SigNoz | Login",
"GET_STARTED": "SigNoz | Get Started",
"SERVICE_METRICS": "SigNoz | Service Metrics",
"SERVICE_MAP": "SigNoz | Service Map",
"TRACE": "SigNoz | Trace",
"TRACE_DETAIL": "SigNoz | Trace Detail",
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
"SETTINGS": "SigNoz | Settings",
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
"APPLICATION": "SigNoz | Home",
"ALL_DASHBOARD": "SigNoz | All Dashboards",
"DASHBOARD": "SigNoz | Dashboard",
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
"EDIT_ALERTS": "SigNoz | Edit Alerts",
"LIST_ALL_ALERT": "SigNoz | All Alerts",
"ALERTS_NEW": "SigNoz | New Alert",
"ALL_CHANNELS": "SigNoz | All Channels",
"CHANNELS_NEW": "SigNoz | New Channel",
"CHANNELS_EDIT": "SigNoz | Edit Channel",
"ALL_ERROR": "SigNoz | All Errors",
"ERROR_DETAIL": "SigNoz | Error Detail",
"VERSION": "SigNoz | Version",
"MY_SETTINGS": "SigNoz | My Settings",
"ORG_SETTINGS": "SigNoz | Organization Settings",
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
"UN_AUTHORIZED": "SigNoz | Unauthorized",
"NOT_FOUND": "SigNoz | Page Not Found",
"LOGS": "SigNoz | Logs",
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
"LIVE_LOGS": "SigNoz | Live Logs",
"HOME_PAGE": "Open source Observability Platform | SigNoz",
"PASSWORD_RESET": "SigNoz | Password Reset",
"LIST_LICENSES": "SigNoz | List of Licenses",
"DEFAULT": "Open source Observability Platform | SigNoz"
}
{
"SIGN_UP": "SigNoz | Sign Up",
"LOGIN": "SigNoz | Login",
"GET_STARTED": "SigNoz | Get Started",
"SERVICE_METRICS": "SigNoz | Service Metrics",
"SERVICE_MAP": "SigNoz | Service Map",
"TRACE": "SigNoz | Trace",
"TRACE_DETAIL": "SigNoz | Trace Detail",
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
"SETTINGS": "SigNoz | Settings",
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
"APPLICATION": "SigNoz | Home",
"ALL_DASHBOARD": "SigNoz | All Dashboards",
"DASHBOARD": "SigNoz | Dashboard",
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
"EDIT_ALERTS": "SigNoz | Edit Alerts",
"LIST_ALL_ALERT": "SigNoz | All Alerts",
"ALERTS_NEW": "SigNoz | New Alert",
"ALL_CHANNELS": "SigNoz | All Channels",
"CHANNELS_NEW": "SigNoz | New Channel",
"CHANNELS_EDIT": "SigNoz | Edit Channel",
"ALL_ERROR": "SigNoz | All Errors",
"ERROR_DETAIL": "SigNoz | Error Detail",
"VERSION": "SigNoz | Version",
"MY_SETTINGS": "SigNoz | My Settings",
"ORG_SETTINGS": "SigNoz | Organization Settings",
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
"UN_AUTHORIZED": "SigNoz | Unauthorized",
"NOT_FOUND": "SigNoz | Page Not Found",
"LOGS": "SigNoz | Logs",
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
"LIVE_LOGS": "SigNoz | Live Logs",
"HOME_PAGE": "Open source Observability Platform | SigNoz",
"PASSWORD_RESET": "SigNoz | Password Reset",
"LIST_LICENSES": "SigNoz | List of Licenses",
"DEFAULT": "Open source Observability Platform | SigNoz"
}

View File

@@ -13,5 +13,12 @@
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
"error_loading_json": "Error loading JSON file",
"empty_json_not_allowed": "Empty JSON is not allowed",
"new_dashboard_title": "Sample Title"
"new_dashboard_title": "Sample Title",
"layout_saved_successfully": "Layout saved successfully",
"add_panel": "Add Panel",
"save_layout": "Save Layout",
"variable_updated_successfully": "Variable updated successfully",
"error_while_updating_variable": "Error while updating variable",
"dashboard_has_been_updated": "Dashboard has been updated",
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?"
}

View File

@@ -25,6 +25,7 @@
"delete_processor_description": "Logs are processed sequentially in processors. Deleting a processor may change content of data processed by other processors",
"search_pipeline_placeholder": "Filter Pipelines",
"pipeline_name_placeholder": "Name",
"pipeline_filter_placeholder": "Filter for selecting logs to be processed by this pipeline. Example: service_name = billing",
"pipeline_tags_placeholder": "Tags",
"pipeline_description_placeholder": "Enter description for your pipeline",
"processor_name_placeholder": "Name",

View File

@@ -1,13 +1,14 @@
{
"general": "General",
"alert_channels": "Alert Channels",
"organization_settings": "Organization Settings",
"my_settings": "My Settings",
"overview_metrics": "Overview Metrics",
"dbcall_metrics": "Database Calls",
"external_metrics": "External Calls",
"pipeline": "Pipeline",
"pipelines": "Pipelines",
"archives": "Archives",
"logs_to_metrics": "Logs To Metrics"
}
{
"general": "General",
"alert_channels": "Alert Channels",
"organization_settings": "Organization Settings",
"ingestion_settings": "Ingestion Settings",
"my_settings": "My Settings",
"overview_metrics": "Overview Metrics",
"dbcall_metrics": "Database Calls",
"external_metrics": "External Calls",
"pipeline": "Pipeline",
"pipelines": "Pipelines",
"archives": "Archives",
"logs_to_metrics": "Logs To Metrics"
}

View File

@@ -1,37 +1,38 @@
{
"SIGN_UP": "SigNoz | Sign Up",
"LOGIN": "SigNoz | Login",
"SERVICE_METRICS": "SigNoz | Service Metrics",
"SERVICE_MAP": "SigNoz | Service Map",
"GET_STARTED": "SigNoz | Get Started",
"TRACE": "SigNoz | Trace",
"TRACE_DETAIL": "SigNoz | Trace Detail",
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
"SETTINGS": "SigNoz | Settings",
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
"APPLICATION": "SigNoz | Home",
"ALL_DASHBOARD": "SigNoz | All Dashboards",
"DASHBOARD": "SigNoz | Dashboard",
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
"EDIT_ALERTS": "SigNoz | Edit Alerts",
"LIST_ALL_ALERT": "SigNoz | All Alerts",
"ALERTS_NEW": "SigNoz | New Alert",
"ALL_CHANNELS": "SigNoz | All Channels",
"CHANNELS_NEW": "SigNoz | New Channel",
"CHANNELS_EDIT": "SigNoz | Edit Channel",
"ALL_ERROR": "SigNoz | All Errors",
"ERROR_DETAIL": "SigNoz | Error Detail",
"VERSION": "SigNoz | Version",
"MY_SETTINGS": "SigNoz | My Settings",
"ORG_SETTINGS": "SigNoz | Organization Settings",
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
"UN_AUTHORIZED": "SigNoz | Unauthorized",
"NOT_FOUND": "SigNoz | Page Not Found",
"LOGS": "SigNoz | Logs",
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
"LIVE_LOGS": "SigNoz | Live Logs",
"HOME_PAGE": "Open source Observability Platform | SigNoz",
"PASSWORD_RESET": "SigNoz | Password Reset",
"LIST_LICENSES": "SigNoz | List of Licenses",
"DEFAULT": "Open source Observability Platform | SigNoz"
}
{
"SIGN_UP": "SigNoz | Sign Up",
"LOGIN": "SigNoz | Login",
"SERVICE_METRICS": "SigNoz | Service Metrics",
"SERVICE_MAP": "SigNoz | Service Map",
"GET_STARTED": "SigNoz | Get Started",
"TRACE": "SigNoz | Trace",
"TRACE_DETAIL": "SigNoz | Trace Detail",
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
"SETTINGS": "SigNoz | Settings",
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
"APPLICATION": "SigNoz | Home",
"ALL_DASHBOARD": "SigNoz | All Dashboards",
"DASHBOARD": "SigNoz | Dashboard",
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
"EDIT_ALERTS": "SigNoz | Edit Alerts",
"LIST_ALL_ALERT": "SigNoz | All Alerts",
"ALERTS_NEW": "SigNoz | New Alert",
"ALL_CHANNELS": "SigNoz | All Channels",
"CHANNELS_NEW": "SigNoz | New Channel",
"CHANNELS_EDIT": "SigNoz | Edit Channel",
"ALL_ERROR": "SigNoz | All Errors",
"ERROR_DETAIL": "SigNoz | Error Detail",
"VERSION": "SigNoz | Version",
"MY_SETTINGS": "SigNoz | My Settings",
"ORG_SETTINGS": "SigNoz | Organization Settings",
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
"UN_AUTHORIZED": "SigNoz | Unauthorized",
"NOT_FOUND": "SigNoz | Page Not Found",
"LOGS": "SigNoz | Logs",
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
"LIVE_LOGS": "SigNoz | Live Logs",
"HOME_PAGE": "Open source Observability Platform | SigNoz",
"PASSWORD_RESET": "SigNoz | Password Reset",
"LIST_LICENSES": "SigNoz | List of Licenses",
"DEFAULT": "Open source Observability Platform | SigNoz"
}

View File

@@ -1,7 +1,10 @@
import { ConfigProvider } from 'antd';
import getLocalStorageApi from 'api/browser/localstorage/get';
import setLocalStorageApi from 'api/browser/localstorage/set';
import NotFound from 'components/NotFound';
import Spinner from 'components/Spinner';
import { FeatureKeys } from 'constants/features';
import { LOCALSTORAGE } from 'constants/localStorage';
import ROUTES from 'constants/routes';
import AppLayout from 'container/AppLayout';
import { useThemeConfig } from 'hooks/useDarkMode';
@@ -9,6 +12,7 @@ import useGetFeatureFlag from 'hooks/useGetFeatureFlag';
import { NotificationProvider } from 'hooks/useNotifications';
import { ResourceProvider } from 'hooks/useResourceAttribute';
import history from 'lib/history';
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
import { QueryBuilderProvider } from 'providers/QueryBuilder';
import { Suspense, useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
@@ -75,14 +79,26 @@ function App(): JSX.Element {
});
useEffect(() => {
if (isLoggedInState && user && user.userId && user.email) {
window.analytics.identify(user?.userId, {
email: user?.email || '',
name: user?.name || '',
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
if (
isLoggedInState &&
user &&
user.userId &&
user.email &&
!isIdentifiedUser
) {
setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
window.analytics.identify(user?.email, {
email: user?.email,
name: user?.name,
});
window.clarity('identify', user.email, user.name);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoggedInState]);
}, [isLoggedInState, user]);
useEffect(() => {
trackPageView(pathname);
@@ -95,22 +111,24 @@ function App(): JSX.Element {
<PrivateRoute>
<ResourceProvider>
<QueryBuilderProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}
<DashboardProvider>
<AppLayout>
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
<Switch>
{routes.map(({ path, component, exact }) => (
<Route
key={`${path}`}
exact={exact}
path={path}
component={component}
/>
))}
<Route path="*" component={NotFound} />
</Switch>
</Suspense>
</AppLayout>
<Route path="*" component={NotFound} />
</Switch>
</Suspense>
</AppLayout>
</DashboardProvider>
</QueryBuilderProvider>
</ResourceProvider>
</PrivateRoute>

View File

@@ -102,6 +102,10 @@ export const OrganizationSettings = Loadable(
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
);
export const IngestionSettings = Loadable(
() => import(/* webpackChunkName: "Ingestion Settings" */ 'pages/Settings'),
);
export const MySettings = Loadable(
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
);

View File

@@ -11,6 +11,7 @@ import {
EditAlertChannelsAlerts,
EditRulesPage,
ErrorDetails,
IngestionSettings,
LicensePage,
ListAllALertsPage,
LiveLogs,
@@ -214,6 +215,13 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'ORG_SETTINGS',
},
{
path: ROUTES.INGESTION_SETTINGS,
exact: true,
component: IngestionSettings,
isPrivate: true,
key: 'INGESTION_SETTINGS',
},
{
path: ROUTES.MY_SETTINGS,
exact: true,

View File

@@ -1,24 +1,9 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Props } from 'types/api/dashboard/delete';
import { PayloadProps, Props } from 'types/api/dashboard/delete';
const deleteDashboard = async (
props: Props,
): Promise<SuccessResponse<undefined> | ErrorResponse> => {
try {
const response = await axios.delete(`/dashboards/${props.uuid}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
const deleteDashboard = (props: Props): Promise<PayloadProps> =>
axios
.delete<PayloadProps>(`/dashboards/${props.uuid}`)
.then((response) => response.data);
export default deleteDashboard;

View File

@@ -1,24 +1,11 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { PayloadProps, Props } from 'types/api/dashboard/get';
import { ApiResponse } from 'types/api';
import { Props } from 'types/api/dashboard/get';
import { Dashboard } from 'types/api/dashboard/getAll';
const get = async (
props: Props,
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
try {
const response = await axios.get(`/dashboards/${props.uuid}`);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
const get = (props: Props): Promise<Dashboard> =>
axios
.get<ApiResponse<Dashboard>>(`/dashboards/${props.uuid}`)
.then((res) => res.data.data);
export default get;

View File

@@ -0,0 +1,21 @@
import axios from 'api';
import { ILog } from 'types/api/logs/log';
import { PipelineData } from 'types/api/pipeline/def';
export interface PipelineSimulationRequest {
logs: ILog[];
pipelines: PipelineData[];
}
export interface PipelineSimulationResponse {
logs: ILog[];
}
const simulatePipelineProcessing = async (
requestBody: PipelineSimulationRequest,
): Promise<PipelineSimulationResponse> =>
axios
.post('/logs/pipelines/preview', requestBody)
.then((res) => res.data.data);
export default simulatePipelineProcessing;

View File

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

View File

@@ -14,7 +14,11 @@ import {
export const Logout = (): void => {
deleteLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN);
deleteLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN);
deleteLocalStorageKey(LOCALSTORAGE.IS_IDENTIFIED_USER);
deleteLocalStorageKey(LOCALSTORAGE.REFRESH_AUTH_TOKEN);
deleteLocalStorageKey(LOCALSTORAGE.LOGGED_IN_USER_EMAIL);
deleteLocalStorageKey(LOCALSTORAGE.LOGGED_IN_USER_NAME);
deleteLocalStorageKey(LOCALSTORAGE.CHAT_SUPPORT);
store.dispatch({
type: LOGGED_IN,

View File

@@ -30,6 +30,7 @@ import { useNotifications } from 'hooks/useNotifications';
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
import { useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { popupContainer } from 'utils/selectPopupContainer';
import { ExploreHeaderToolTip, SaveButtonText } from './constants';
import MenuItemGenerator from './MenuItemGenerator';
@@ -170,6 +171,7 @@ function ExplorerCard({
{viewsData?.data.data && viewsData?.data.data.length && (
<Space>
<Select
getPopupContainer={popupContainer}
loading={isLoading || isRefetching}
showSearch
placeholder="Select a view"
@@ -204,6 +206,7 @@ function ExplorerCard({
</Button>
)}
<Popover
getPopupContainer={popupContainer}
placement="bottomLeft"
trigger="click"
content={

View File

@@ -1,5 +1,5 @@
import { DeleteOutlined } from '@ant-design/icons';
import { Col, Row, Typography } from 'antd';
import { Col, Row, Tooltip, Typography } from 'antd';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
@@ -8,7 +8,11 @@ import { MouseEvent, useCallback } from 'react';
import { MenuItemContainer } from './styles';
import { MenuItemLabelGeneratorProps } from './types';
import { deleteViewHandler, getViewDetailsUsingViewKey } from './utils';
import {
deleteViewHandler,
getViewDetailsUsingViewKey,
trimViewName,
} from './utils';
function MenuItemGenerator({
viewName,
@@ -71,12 +75,16 @@ function MenuItemGenerator({
});
};
const newViewName = trimViewName(viewName);
return (
<MenuItemContainer onClick={onLabelClickHandler}>
<Row justify="space-between">
<Col span={22}>
<Row>
<Typography.Text strong>{viewName}</Typography.Text>
<Tooltip title={viewName}>
<Typography.Text strong>{newViewName}</Typography.Text>
</Tooltip>
</Row>
<Row>
<Typography.Text type="secondary">Created by {createdBy}</Typography.Text>

View File

@@ -174,3 +174,10 @@ export const deleteViewHandler = ({
},
});
};
export const trimViewName = (viewName: string): string => {
if (viewName.length > 20) {
return `${viewName.substring(0, 20)}...`;
}
return viewName;
};

View File

@@ -60,12 +60,14 @@ function RawLogView({
const isDarkMode = useIsDarkMode();
const isReadOnlyLog = !isLogsExplorerPage || isReadOnly;
const severityText = data.severity_text ? `${data.severity_text} |` : '';
const text = useMemo(
() =>
typeof data.timestamp === 'string'
? `${dayjs(data.timestamp).format()} | ${data.body}`
: `${dayjs(data.timestamp / 1e6).format()} | ${data.body}`,
[data.timestamp, data.body],
? `${dayjs(data.timestamp).format()} | ${severityText} ${data.body}`
: `${dayjs(data.timestamp / 1e6).format()} | ${severityText} ${data.body}`,
[data.timestamp, data.body, severityText],
);
const handleClickExpand = useCallback(() => {
@@ -114,11 +116,6 @@ function RawLogView({
[text],
);
const mouseActions = useMemo(
() => ({ onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave }),
[handleMouseEnter, handleMouseLeave],
);
return (
<RawLogViewContainer
onClick={handleClickExpand}
@@ -127,8 +124,8 @@ function RawLogView({
$isDarkMode={isDarkMode}
$isReadOnly={isReadOnly}
$isActiveLog={isHighlighted}
// eslint-disable-next-line react/jsx-props-no-spreading
{...mouseActions}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{!isReadOnly && (
<ExpandIconWrapper flex="30px">

View File

@@ -0,0 +1,34 @@
.code-snippet-container {
position: relative;
background-color: rgb(43, 43, 43);
}
.code-copy-btn {
position: absolute;
top: 8px;
right: 8px;
display: flex;
justify-content: flex-end;
align-items: center;
button {
cursor: pointer;
background-color: rgba($color: #1d1d1d, $alpha: 0.7);
color: white;
border: none;
padding: 8px;
border-radius: 3px;
transition: all 0.1s;
&:hover {
background-color: rgba($color: #1d1d1d, $alpha: 1);
}
}
&.copied {
button {
background-color: rgba($color: #52c41a, $alpha: 1);
}
}
}

View File

@@ -0,0 +1,32 @@
import './CodeCopyBtn.scss';
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import cx from 'classnames';
import { useState } from 'react';
export default function CodeCopyBtn({
children,
}: {
children: React.ReactNode;
}): JSX.Element {
const [isSnippetCopied, setIsSnippetCopied] = useState(false);
const handleClick = (): void => {
if (children && Array.isArray(children)) {
setIsSnippetCopied(true);
navigator.clipboard.writeText(children[0].props.children[0]).finally(() => {
setTimeout(() => {
setIsSnippetCopied(false);
}, 1000);
});
}
};
return (
<div className={cx('code-copy-btn', isSnippetCopied ? 'copied' : '')}>
<button type="button" onClick={handleClick}>
{!isSnippetCopied ? <CopyOutlined /> : <CheckOutlined />}
</button>
</div>
);
}

View File

@@ -0,0 +1,101 @@
/* eslint-disable no-restricted-syntax */
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import ReactMarkdown from 'react-markdown';
import { CodeProps } from 'react-markdown/lib/ast-to-react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { a11yDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import CodeCopyBtn from './CodeCopyBtn/CodeCopyBtn';
interface LinkProps {
href: string;
children: React.ReactElement;
}
function Pre({ children }: { children: React.ReactNode }): JSX.Element {
return (
<pre className="code-snippet-container">
<CodeCopyBtn>{children}</CodeCopyBtn>
{children}
</pre>
);
}
function Code({
node,
inline,
className = 'blog-code',
children,
...props
}: CodeProps): JSX.Element {
const match = /language-(\w+)/.exec(className || '');
return !inline && match ? (
<SyntaxHighlighter
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
style={a11yDark}
language={match[1]}
PreTag="div"
{...props}
>
{String(children).replace(/\n$/, '')}
</SyntaxHighlighter>
) : (
<code className={className} {...props}>
{children}
</code>
);
}
function Link({ href, children }: LinkProps): JSX.Element {
return (
<a href={href} target="_blank" rel="noopener noreferrer">
{children}
</a>
);
}
const interpolateMarkdown = (
markdownContent: any,
variables: { [s: string]: unknown } | ArrayLike<unknown>,
) => {
let interpolatedContent = markdownContent;
const variableEntries = Object.entries(variables);
// Loop through variables and replace placeholders with values
for (const [key, value] of variableEntries) {
const placeholder = `{{${key}}}`;
const regex = new RegExp(placeholder, 'g');
interpolatedContent = interpolatedContent.replace(regex, value);
}
return interpolatedContent;
};
function MarkdownRenderer({
markdownContent,
variables,
}: {
markdownContent: any;
variables: any;
}): JSX.Element {
const interpolatedMarkdown = interpolateMarkdown(markdownContent, variables);
return (
<ReactMarkdown
components={{
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
a: Link,
pre: Pre,
code: Code,
}}
>
{interpolatedMarkdown}
</ReactMarkdown>
);
}
export { Code, Link, MarkdownRenderer, Pre };

View File

@@ -0,0 +1,3 @@
.overlay--text-wrap {
white-space: pre-wrap;
}

View File

@@ -0,0 +1,89 @@
import './TextToolTip.style.scss';
import { blue, grey } from '@ant-design/colors';
import {
QuestionCircleFilled,
QuestionCircleOutlined,
} from '@ant-design/icons';
import { Tooltip } from 'antd';
import { themeColors } from 'constants/theme';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useMemo } from 'react';
import { popupContainer } from 'utils/selectPopupContainer';
import { style } from './constant';
function TextToolTip({
text,
url,
useFilledIcon = true,
urlText,
}: TextToolTipProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const onClickHandler = (
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
): void => {
event.stopPropagation();
};
const overlay = useMemo(
() => (
<div className="overlay--text-wrap">
{`${text} `}
{url && (
<a
// Stopping event propagation on click so that parent click listener are not triggered
onClick={onClickHandler}
href={url}
rel="noopener noreferrer"
target="_blank"
>
{urlText || 'here'}
</a>
)}
</div>
),
[text, url, urlText],
);
const iconStyle = useMemo(
() => ({
...style,
color: isDarkMode ? themeColors.whiteCream : grey[0],
}),
[isDarkMode],
);
const iconOutlinedStyle = useMemo(
() => ({
...style,
color: isDarkMode ? themeColors.navyBlue : blue[6],
}),
[isDarkMode],
);
return (
<Tooltip getTooltipContainer={popupContainer} overlay={overlay}>
{useFilledIcon ? (
<QuestionCircleFilled style={iconStyle} />
) : (
<QuestionCircleOutlined style={iconOutlinedStyle} />
)}
</Tooltip>
);
}
TextToolTip.defaultProps = {
url: '',
urlText: '',
useFilledIcon: true,
};
interface TextToolTipProps {
url?: string;
text: string;
useFilledIcon?: boolean;
urlText?: string;
}
export default TextToolTip;

View File

@@ -1,86 +1,3 @@
import { blue, grey } from '@ant-design/colors';
import {
QuestionCircleFilled,
QuestionCircleOutlined,
} from '@ant-design/icons';
import { Tooltip } from 'antd';
import { themeColors } from 'constants/theme';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useMemo } from 'react';
import { style } from './styles';
function TextToolTip({
text,
url,
useFilledIcon = true,
urlText,
}: TextToolTipProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const onClickHandler = (
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
): void => {
event.stopPropagation();
};
const overlay = useMemo(
() => (
<div>
{`${text} `}
{url && (
<a
// Stopping event propagation on click so that parent click listener are not triggered
onClick={onClickHandler}
href={url}
rel="noopener noreferrer"
target="_blank"
>
{urlText || 'here'}
</a>
)}
</div>
),
[text, url, urlText],
);
const iconStyle = useMemo(
() => ({
...style,
color: isDarkMode ? themeColors.whiteCream : grey[0],
}),
[isDarkMode],
);
const iconOutlinedStyle = useMemo(
() => ({
...style,
color: isDarkMode ? themeColors.navyBlue : blue[6],
}),
[isDarkMode],
);
return (
<Tooltip overlay={overlay}>
{useFilledIcon ? (
<QuestionCircleFilled style={iconStyle} />
) : (
<QuestionCircleOutlined style={iconOutlinedStyle} />
)}
</Tooltip>
);
}
TextToolTip.defaultProps = {
url: '',
urlText: '',
useFilledIcon: true,
};
interface TextToolTipProps {
url?: string;
text: string;
useFilledIcon?: boolean;
urlText?: string;
}
import TextToolTip from './TextToolTip';
export default TextToolTip;

View File

@@ -14,4 +14,5 @@ export enum LOCALSTORAGE {
LOGGED_IN_USER_NAME = 'LOGGED_IN_USER_NAME',
LOGGED_IN_USER_EMAIL = 'LOGGED_IN_USER_EMAIL',
CHAT_SUPPORT = 'CHAT_SUPPORT',
IS_IDENTIFIED_USER = 'IS_IDENTIFIED_USER',
}

View File

@@ -74,7 +74,7 @@ export const mapOfOperators = {
traces: tracesAggregateOperatorOptions,
};
export const mapOfFilters: Record<DataSource, QueryAdditionalFilter[]> = {
export const mapOfQueryFilters: Record<DataSource, QueryAdditionalFilter[]> = {
metrics: [
// eslint-disable-next-line sonarjs/no-duplicate-string
{ text: 'Aggregation interval', field: 'stepInterval' },
@@ -94,6 +94,24 @@ export const mapOfFilters: Record<DataSource, QueryAdditionalFilter[]> = {
],
};
const commonFormulaFilters: QueryAdditionalFilter[] = [
{
text: 'Having',
field: 'having',
},
{ text: 'Order by', field: 'orderBy' },
{ text: 'Limit', field: 'limit' },
];
export const mapOfFormulaToFilters: Record<
DataSource,
QueryAdditionalFilter[]
> = {
metrics: commonFormulaFilters,
logs: commonFormulaFilters,
traces: commonFormulaFilters,
};
export const REDUCE_TO_VALUES: SelectOption<ReduceOperators, string>[] = [
{ value: 'last', label: 'Latest of values in timeframe' },
{ value: 'sum', label: 'Sum of values in timeframe' },

View File

@@ -3,5 +3,8 @@ export const REACT_QUERY_KEY = {
GET_QUERY_RANGE: 'GET_QUERY_RANGE',
GET_ALL_DASHBOARDS: 'GET_ALL_DASHBOARDS',
GET_TRIGGERED_ALERTS: 'GET_TRIGGERED_ALERTS',
DASHBOARD_BY_ID: 'DASHBOARD_BY_ID',
GET_FEATURES_FLAGS: 'GET_FEATURES_FLAGS',
DELETE_DASHBOARD: 'DELETE_DASHBOARD',
LOGS_PIPELINE_PREVIEW: 'LOGS_PIPELINE_PREVIEW',
};

View File

@@ -24,6 +24,7 @@ const ROUTES = {
VERSION: '/status',
MY_SETTINGS: '/my-settings',
ORG_SETTINGS: '/settings/org-settings',
INGESTION_SETTINGS: '/settings/ingestion-settings',
SOMETHING_WENT_WRONG: '/something-went-wrong',
UN_AUTHORIZED: '/un-authorized',
NOT_FOUND: '/not-found',

View File

@@ -2,6 +2,7 @@ import { LeftOutlined, RightOutlined } from '@ant-design/icons';
import { Button, Select } from 'antd';
import { DEFAULT_PER_PAGE_OPTIONS, Pagination } from 'hooks/queryPagination';
import { memo, useMemo } from 'react';
import { popupContainer } from 'utils/selectPopupContainer';
import { defaultSelectStyle } from './config';
import { Container } from './styles';
@@ -51,6 +52,7 @@ function Controls({
loading={isLoading}
value={countPerPage}
onChange={handleCountItemsPerPageChange}
getPopupContainer={popupContainer}
>
{perPageOptions.map((count) => (
<Select.Option

View File

@@ -3,6 +3,7 @@ import {
initialQueryPromQLData,
PANEL_TYPES,
} from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import {
AlertDef,
@@ -77,7 +78,7 @@ export const logAlertDefaults: AlertDef = {
},
labels: {
severity: 'warning',
details: `${window.location.protocol}//${window.location.host}/logs`,
details: `${window.location.protocol}//${window.location.host}${ROUTES.LOGS_EXPLORER}`,
},
annotations: defaultAnnotations,
evalWindow: defaultEvalWindow,

View File

@@ -1,3 +1,5 @@
import './styles.scss';
import { Button, Divider, Space, Typography } from 'antd';
import getNextPrevId from 'api/errors/getNextPrevId';
import Editor from 'components/Editor';
@@ -161,7 +163,9 @@ function ErrorDetails(props: ErrorDetailsProps): JSX.Element {
</DashedContainer>
<Typography.Title level={4}>{t('stack_trace')}</Typography.Title>
<Editor onChange={(): void => {}} value={stackTraceValue} readOnly />
<div className="error-container">
<Editor value={stackTraceValue} readOnly />
</div>
<EditorContainer>
<Space direction="vertical">

View File

@@ -0,0 +1,3 @@
.error-container {
height: 50vh;
}

View File

@@ -25,6 +25,7 @@ export interface ChartPreviewProps {
headline?: JSX.Element;
alertDef?: AlertDef;
userQueryKey?: string;
allowSelectedIntervalForStepGen?: boolean;
}
function ChartPreview({
@@ -35,6 +36,7 @@ function ChartPreview({
selectedInterval = '5min',
headline,
userQueryKey,
allowSelectedIntervalForStepGen = false,
alertDef,
}: ChartPreviewProps): JSX.Element | null {
const { t } = useTranslation('alerts');
@@ -89,6 +91,9 @@ function ChartPreview({
globalSelectedInterval: selectedInterval,
graphType,
selectedTime,
params: {
allowSelectedIntervalForStepGen,
},
},
{
queryKey: [
@@ -127,7 +132,7 @@ function ChartPreview({
<GridPanelSwitch
panelType={graphType}
title={name}
data={chartDataSet}
data={chartDataSet.data}
isStacked
name={name || 'Chart Preview'}
staticLine={staticLine}
@@ -146,6 +151,7 @@ ChartPreview.defaultProps = {
selectedInterval: '5min',
headline: undefined,
userQueryKey: '',
allowSelectedIntervalForStepGen: false,
alertDef: undefined,
};

View File

@@ -44,7 +44,7 @@ import {
StyledLeftContainer,
} from './styles';
import UserGuide from './UserGuide';
import { toChartInterval } from './utils';
import { getUpdatedStepInterval, toChartInterval } from './utils';
function FormAlertRules({
alertType,
@@ -354,6 +354,16 @@ function FormAlertRules({
<BasicInfo alertDef={alertDef} setAlertDef={setAlertDef} />
);
const updatedStagedQuery = useMemo((): Query | null => {
const newQuery: Query | null = stagedQuery;
if (newQuery) {
newQuery.builder.queryData[0].stepInterval = getUpdatedStepInterval(
alertDef.evalWindow,
);
}
return newQuery;
}, [alertDef.evalWindow, stagedQuery]);
const renderQBChartPreview = (): JSX.Element => (
<ChartPreview
headline={
@@ -363,9 +373,10 @@ function FormAlertRules({
/>
}
name=""
query={stagedQuery}
query={updatedStagedQuery}
selectedInterval={toChartInterval(alertDef.evalWindow)}
alertDef={alertDef}
allowSelectedIntervalForStepGen
/>
);

View File

@@ -0,0 +1,14 @@
// Write a test for getUpdatedStepInterval function in src/container/FormAlertRules/utils.ts
import { getUpdatedStepInterval } from './utils';
describe('getUpdatedStepInterval', () => {
it('should return 60', () => {
const result = getUpdatedStepInterval('5m0s');
expect(result).toEqual(60);
});
it('should return 60 for 10m0s', () => {
const result = getUpdatedStepInterval('10m0s');
expect(result).toEqual(60);
});
});

View File

@@ -1,4 +1,6 @@
import { Time } from 'container/TopNav/DateTimeSelection/config';
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
import getStep from 'lib/getStep';
// toChartInterval converts eval window to chart selection time interval
export const toChartInterval = (evalWindow: string | undefined): Time => {
@@ -21,3 +23,15 @@ export const toChartInterval = (evalWindow: string | undefined): Time => {
return '5min';
}
};
export const getUpdatedStepInterval = (evalWindow?: string): number => {
const { start, end } = getStartEndRangeTime({
type: 'GLOBAL_TIME',
interval: toChartInterval(evalWindow),
});
return getStep({
start,
end,
inputFormat: 'ns',
});
};

View File

@@ -0,0 +1,21 @@
.graph-manager-container {
margin-top: 1.25rem;
display: flex;
align-items: flex-end;
overflow-x: scroll;
.filter-table-container {
flex-basis: 80%;
}
.save-cancel-container {
flex-basis: 20%;
display: flex;
justify-content: flex-end;
}
.save-cancel-button {
margin: 0 0.313rem;
}
}

View File

@@ -0,0 +1,136 @@
import './GraphManager.styles.scss';
import { Button, Input } from 'antd';
import { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ResizeTable } from 'components/ResizeTable';
import { useNotifications } from 'hooks/useNotifications';
import { memo, useCallback, useState } from 'react';
import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns';
import { ExtendedChartDataset, GraphManagerProps } from './types';
import {
getDefaultTableDataSet,
saveLegendEntriesToLocalStorage,
} from './utils';
function GraphManager({
data,
name,
yAxisUnit,
onToggleModelHandler,
setGraphsVisibilityStates,
graphsVisibilityStates = [],
lineChartRef,
parentChartRef,
}: GraphManagerProps): JSX.Element {
const [tableDataSet, setTableDataSet] = useState<ExtendedChartDataset[]>(
getDefaultTableDataSet(data),
);
const { notifications } = useNotifications();
const checkBoxOnChangeHandler = useCallback(
(e: CheckboxChangeEvent, index: number): void => {
const newStates = [...graphsVisibilityStates];
newStates[index] = e.target.checked;
lineChartRef?.current?.toggleGraph(index, e.target.checked);
setGraphsVisibilityStates([...newStates]);
},
[graphsVisibilityStates, setGraphsVisibilityStates, lineChartRef],
);
const labelClickedHandler = useCallback(
(labelIndex: number): void => {
const newGraphVisibilityStates = Array<boolean>(data.datasets.length).fill(
false,
);
newGraphVisibilityStates[labelIndex] = true;
newGraphVisibilityStates.forEach((state, index) => {
lineChartRef?.current?.toggleGraph(index, state);
parentChartRef?.current?.toggleGraph(index, state);
});
setGraphsVisibilityStates(newGraphVisibilityStates);
},
[
data.datasets.length,
setGraphsVisibilityStates,
lineChartRef,
parentChartRef,
],
);
const columns = getGraphManagerTableColumns({
data,
checkBoxOnChangeHandler,
graphVisibilityState: graphsVisibilityStates || [],
labelClickedHandler,
yAxisUnit,
});
const filterHandler = useCallback(
(event: React.ChangeEvent<HTMLInputElement>): void => {
const value = event.target.value.toString().toLowerCase();
const updatedDataSet = tableDataSet.map((item) => {
if (item.label?.toLocaleLowerCase().includes(value)) {
return { ...item, show: true };
}
return { ...item, show: false };
});
setTableDataSet(updatedDataSet);
},
[tableDataSet],
);
const saveHandler = useCallback((): void => {
saveLegendEntriesToLocalStorage({
data,
graphVisibilityState: graphsVisibilityStates || [],
name,
});
notifications.success({
message: 'The updated graphs & legends are saved',
});
if (onToggleModelHandler) {
onToggleModelHandler();
}
}, [data, graphsVisibilityStates, name, notifications, onToggleModelHandler]);
const dataSource = tableDataSet.filter((item) => item.show);
return (
<div className="graph-manager-container">
<div className="filter-table-container">
<Input onChange={filterHandler} placeholder="Filter Series" />
<ResizeTable
columns={columns}
dataSource={dataSource}
rowKey="index"
pagination={false}
scroll={{ y: 240 }}
/>
</div>
<div className="save-cancel-container">
<span className="save-cancel-button">
<Button type="default" onClick={onToggleModelHandler}>
Cancel
</Button>
</span>
<span className="save-cancel-button">
<Button onClick={saveHandler} type="primary">
Save
</Button>
</span>
</div>
</div>
);
}
GraphManager.defaultProps = {
graphVisibilityStateHandler: undefined,
};
export default memo(GraphManager);

View File

@@ -1,3 +1,4 @@
import { grey } from '@ant-design/colors';
import { Checkbox, ConfigProvider } from 'antd';
import { CheckboxChangeEvent } from 'antd/es/checkbox';
@@ -6,7 +7,7 @@ import { CheckBoxProps } from '../types';
function CustomCheckBox({
data,
index,
graphVisibilityState,
graphVisibilityState = [],
checkBoxOnChangeHandler,
}: CheckBoxProps): JSX.Element {
const { datasets } = data;
@@ -15,17 +16,21 @@ function CustomCheckBox({
checkBoxOnChangeHandler(e, index);
};
const color = datasets[index]?.borderColor?.toString() || grey[0];
const isChecked = graphVisibilityState[index] || false;
return (
<ConfigProvider
theme={{
token: {
colorPrimary: datasets[index].borderColor?.toString(),
colorBorder: datasets[index].borderColor?.toString(),
colorBgContainer: datasets[index].borderColor?.toString(),
colorPrimary: color,
colorBorder: color,
colorBgContainer: color,
},
}}
>
<Checkbox onChange={onChangeHandler} checked={graphVisibilityState[index]} />
<Checkbox onChange={onChangeHandler} checked={isChecked} />
</ConfigProvider>
);
}

View File

@@ -5,7 +5,7 @@ import { ChartData } from 'chart.js';
import { ColumnsKeyAndDataIndex, ColumnsTitle } from '../contants';
import { DataSetProps } from '../types';
import { getGraphManagerTableHeaderTitle } from '../utils';
import { getCheckBox } from './GetCheckBox';
import CustomCheckBox from './CustomCheckBox';
import { getLabel } from './GetLabel';
export const getGraphManagerTableColumns = ({
@@ -20,11 +20,14 @@ export const getGraphManagerTableColumns = ({
width: 50,
dataIndex: ColumnsKeyAndDataIndex.Index,
key: ColumnsKeyAndDataIndex.Index,
...getCheckBox({
checkBoxOnChangeHandler,
graphVisibilityState,
data,
}),
render: (_: string, __: DataSetProps, index: number): JSX.Element => (
<CustomCheckBox
data={data}
index={index}
checkBoxOnChangeHandler={checkBoxOnChangeHandler}
graphVisibilityState={graphVisibilityState}
/>
),
},
{
title: ColumnsTitle[ColumnsKeyAndDataIndex.Label],

View File

@@ -12,17 +12,16 @@ import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { useChartMutable } from 'hooks/useChartMutable';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import getChartData from 'lib/getChartData';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import { toggleGraphsVisibilityInChart } from '../utils';
import { PANEL_TYPES_VS_FULL_VIEW_TABLE } from './contants';
import GraphManager from './GraphManager';
import { GraphContainer, TimeContainer } from './styles';
import { FullViewProps } from './types';
import { getIsGraphLegendToggleAvailable } from './utils';
function FullView({
widget,
@@ -34,45 +33,29 @@ function FullView({
isDependedDataLoaded = false,
graphsVisibilityStates,
onToggleModelHandler,
setGraphsVisibilityStates,
parentChartRef,
}: FullViewProps): JSX.Element {
const { selectedTime: globalSelectedTime } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const { selectedDashboard } = useDashboard();
const getSelectedTime = useCallback(
() =>
timeItems.find((e) => e.enum === (widget?.timePreferance || 'GLOBAL_TIME')),
[widget],
);
const canModifyChart = useChartMutable({
panelType: widget.panelTypes,
panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE,
});
const lineChartRef = useRef<ToggleGraphProps>();
useEffect(() => {
if (graphsVisibilityStates && canModifyChart && lineChartRef.current) {
toggleGraphsVisibilityInChart({
graphsVisibilityStates,
lineChartRef,
});
}
}, [graphsVisibilityStates, canModifyChart]);
const [selectedTime, setSelectedTime] = useState<timePreferance>({
name: getSelectedTime()?.name || '',
enum: widget?.timePreferance || 'GLOBAL_TIME',
});
const queryKey = useMemo(
() =>
`FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`,
[selectedTime, globalSelectedTime, widget],
);
const updatedQuery = useStepInterval(widget?.query);
const response = useGetQueryRange(
@@ -81,14 +64,19 @@ function FullView({
graphType: widget.panelTypes,
query: updatedQuery,
globalSelectedInterval: globalSelectedTime,
variables: getDashboardVariables(),
variables: getDashboardVariables(selectedDashboard?.data.variables),
},
{
queryKey,
queryKey: `FullViewGetMetricsQueryRange-${selectedTime.enum}-${globalSelectedTime}-${widget.id}`,
enabled: !isDependedDataLoaded,
},
);
const canModifyChart = useChartMutable({
panelType: widget.panelTypes,
panelTypeAndGraphManagerVisibility: PANEL_TYPES_VS_FULL_VIEW_TABLE,
});
const chartDataSet = useMemo(
() =>
getChartData({
@@ -101,9 +89,14 @@ function FullView({
[response],
);
const isGraphLegendToggleAvailable = getIsGraphLegendToggleAvailable(
widget.panelTypes,
);
useEffect(() => {
if (!response.isFetching && lineChartRef.current) {
graphsVisibilityStates?.forEach((e, i) => {
lineChartRef?.current?.toggleGraph(i, e);
parentChartRef?.current?.toggleGraph(i, e);
});
}
}, [graphsVisibilityStates, parentChartRef, response.isFetching]);
if (response.isFetching) {
return <Spinner height="100%" size="large" tip="Loading..." />;
@@ -128,10 +121,10 @@ function FullView({
</TimeContainer>
)}
<GraphContainer isGraphLegendToggleAvailable={isGraphLegendToggleAvailable}>
<GraphContainer isGraphLegendToggleAvailable={canModifyChart}>
<GridPanelSwitch
panelType={widget.panelTypes}
data={chartDataSet}
data={chartDataSet.data}
isStacked={widget.isStacked}
opacity={widget.opacity}
title={widget.title}
@@ -147,10 +140,14 @@ function FullView({
{canModifyChart && (
<GraphManager
data={chartDataSet}
data={chartDataSet.data}
name={name}
yAxisUnit={yAxisUnit}
onToggleModelHandler={onToggleModelHandler}
setGraphsVisibilityStates={setGraphsVisibilityStates}
graphsVisibilityStates={graphsVisibilityStates}
lineChartRef={lineChartRef}
parentChartRef={parentChartRef}
/>
)}
</>

View File

@@ -31,26 +31,6 @@ export const GraphContainer = styled.div<GraphContainerProps>`
isGraphLegendToggleAvailable ? '50%' : '100%'};
`;
export const FilterTableAndSaveContainer = styled.div`
margin-top: 1.875rem;
display: flex;
align-items: flex-end;
`;
export const FilterTableContainer = styled.div`
flex-basis: 80%;
`;
export const SaveContainer = styled.div`
flex-basis: 20%;
display: flex;
justify-content: flex-end;
`;
export const SaveCancelButtonContainer = styled.span`
margin: 0 0.313rem;
`;
export const LabelContainer = styled.button`
max-width: 18.75rem;
cursor: pointer;

View File

@@ -1,7 +1,8 @@
import { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ChartData, ChartDataset } from 'chart.js';
import { GraphOnClickHandler } from 'components/Graph/types';
import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { MutableRefObject } from 'react';
import { Widgets } from 'types/api/dashboard/getAll';
export interface DataSetProps {
@@ -40,20 +41,6 @@ export interface LabelProps {
label: string;
}
export interface GraphManagerProps {
data: ChartData;
name: string;
yAxisUnit?: string;
onToggleModelHandler?: () => void;
}
export interface CheckBoxProps {
data: ChartData;
index: number;
graphVisibilityState: boolean[];
checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void;
}
export interface FullViewProps {
widget: Widgets;
fullViewOptions?: boolean;
@@ -64,6 +51,26 @@ export interface FullViewProps {
isDependedDataLoaded?: boolean;
graphsVisibilityStates?: boolean[];
onToggleModelHandler?: GraphManagerProps['onToggleModelHandler'];
setGraphsVisibilityStates: (graphsVisibilityStates: boolean[]) => void;
parentChartRef: GraphManagerProps['lineChartRef'];
}
export interface GraphManagerProps {
data: ChartData;
name: string;
yAxisUnit?: string;
onToggleModelHandler?: () => void;
setGraphsVisibilityStates: FullViewProps['setGraphsVisibilityStates'];
graphsVisibilityStates: FullViewProps['graphsVisibilityStates'];
lineChartRef?: MutableRefObject<ToggleGraphProps | undefined>;
parentChartRef?: MutableRefObject<ToggleGraphProps | undefined>;
}
export interface CheckBoxProps {
data: ChartData;
index: number;
graphVisibilityState: boolean[];
checkBoxOnChangeHandler: (e: CheckboxChangeEvent, index: number) => void;
}
export interface SaveLegendEntriesToLocalStoreProps {

View File

@@ -1,6 +1,5 @@
import { ChartData, ChartDataset } from 'chart.js';
import { LOCALSTORAGE } from 'constants/localStorage';
import { PANEL_TYPES } from 'constants/queryBuilder';
import {
ExtendedChartDataset,
@@ -110,10 +109,6 @@ export const saveLegendEntriesToLocalStorage = ({
}
};
export const getIsGraphLegendToggleAvailable = (
panelType: PANEL_TYPES,
): boolean => panelType === PANEL_TYPES.TIME_SERIES;
export const getGraphManagerTableHeaderTitle = (
title: string,
yAxisUnit?: string,

View File

@@ -0,0 +1,277 @@
import { Typography } from 'antd';
import { ToggleGraphProps } from 'components/Graph/types';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import GridPanelSwitch from 'container/GridPanelSwitch';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import { useNotifications } from 'hooks/useNotifications';
import createQueryParams from 'lib/createQueryParams';
import history from 'lib/history';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { AppState } from 'store/reducers';
import { Dashboard } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import { v4 } from 'uuid';
import WidgetHeader from '../WidgetHeader';
import FullView from './FullView';
import { FullViewContainer, Modal } from './styles';
import { WidgetGraphComponentProps } from './types';
import { getGraphVisibilityStateOnDataChange } from './utils';
function WidgetGraphComponent({
data,
widget,
queryResponse,
errorMessage,
name,
onDragSelect,
onClickHandler,
threshold,
headerMenuList,
isWarning,
}: WidgetGraphComponentProps): JSX.Element {
const [deleteModal, setDeleteModal] = useState(false);
const [modal, setModal] = useState<boolean>(false);
const [hovered, setHovered] = useState(false);
const { notifications } = useNotifications();
const { pathname } = useLocation();
const lineChartRef = useRef<ToggleGraphProps>();
const { graphVisibilityStates: localStoredVisibilityStates } = useMemo(
() =>
getGraphVisibilityStateOnDataChange({
data,
isExpandedName: true,
name,
}),
[data, name],
);
useEffect(() => {
if (!lineChartRef.current) return;
localStoredVisibilityStates.forEach((state, index) => {
lineChartRef.current?.toggleGraph(index, state);
});
}, [localStoredVisibilityStates]);
const { setLayouts, selectedDashboard, setSelectedDashboard } = useDashboard();
const [graphsVisibilityStates, setGraphsVisibilityStates] = useState<
boolean[]
>(localStoredVisibilityStates);
const { featureResponse } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const onToggleModal = useCallback(
(func: Dispatch<SetStateAction<boolean>>) => {
func((value) => !value);
},
[],
);
const updateDashboardMutation = useUpdateDashboard();
const onDeleteHandler = (): void => {
if (!selectedDashboard) return;
const updatedWidgets = selectedDashboard?.data?.widgets?.filter(
(e) => e.id !== widget.id,
);
const updatedLayout =
selectedDashboard.data.layout?.filter((e) => e.i !== widget.id) || [];
const updatedSelectedDashboard: Dashboard = {
...selectedDashboard,
data: {
...selectedDashboard.data,
widgets: updatedWidgets,
layout: updatedLayout,
},
uuid: selectedDashboard.uuid,
};
updateDashboardMutation.mutateAsync(updatedSelectedDashboard, {
onSuccess: (updatedDashboard) => {
if (setLayouts) setLayouts(updatedDashboard.payload?.data?.layout || []);
if (setSelectedDashboard && updatedDashboard.payload) {
setSelectedDashboard(updatedDashboard.payload);
}
featureResponse.refetch();
},
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
});
};
const onCloneHandler = async (): Promise<void> => {
if (!selectedDashboard) return;
const uuid = v4();
const layout = [
...(selectedDashboard.data.layout || []),
{
i: uuid,
w: 6,
x: 0,
h: 2,
y: 0,
},
];
updateDashboardMutation.mutateAsync(
{
...selectedDashboard,
data: {
...selectedDashboard.data,
layout,
widgets: [
...(selectedDashboard.data.widgets || []),
{
...{
...widget,
id: uuid,
},
},
],
},
},
{
onSuccess: () => {
notifications.success({
message: 'Panel cloned successfully, redirecting to new copy.',
});
const queryParams = {
graphType: widget?.panelTypes,
widgetId: uuid,
};
history.push(`${pathname}/new?${createQueryParams(queryParams)}`);
},
},
);
};
const handleOnView = (): void => {
onToggleModal(setModal);
};
const handleOnDelete = (): void => {
onToggleModal(setDeleteModal);
};
const onDeleteModelHandler = (): void => {
onToggleModal(setDeleteModal);
};
const onToggleModelHandler = (): void => {
onToggleModal(setModal);
};
return (
<span
onMouseOver={(): void => {
setHovered(true);
}}
onFocus={(): void => {
setHovered(true);
}}
onMouseOut={(): void => {
setHovered(false);
}}
onBlur={(): void => {
setHovered(false);
}}
>
<Modal
destroyOnClose
onCancel={onDeleteModelHandler}
open={deleteModal}
title="Delete"
height="10vh"
onOk={onDeleteHandler}
centered
>
<Typography>Are you sure you want to delete this widget</Typography>
</Modal>
<Modal
title="View"
footer={[]}
centered
open={modal}
onCancel={onToggleModelHandler}
width="85%"
destroyOnClose
>
<FullViewContainer>
<FullView
name={`${name}expanded`}
widget={widget}
yAxisUnit={widget.yAxisUnit}
graphsVisibilityStates={graphsVisibilityStates}
onToggleModelHandler={onToggleModelHandler}
setGraphsVisibilityStates={setGraphsVisibilityStates}
parentChartRef={lineChartRef}
/>
</FullViewContainer>
</Modal>
<div className="drag-handle">
<WidgetHeader
parentHover={hovered}
title={widget?.title}
widget={widget}
onView={handleOnView}
onDelete={handleOnDelete}
onClone={onCloneHandler}
queryResponse={queryResponse}
errorMessage={errorMessage}
threshold={threshold}
headerMenuList={headerMenuList}
isWarning={isWarning}
/>
</div>
<GridPanelSwitch
panelType={widget.panelTypes}
data={data}
isStacked={widget.isStacked}
opacity={widget.opacity}
title={' '}
name={name}
yAxisUnit={widget.yAxisUnit}
onClickHandler={onClickHandler}
onDragSelect={onDragSelect}
panelData={queryResponse.data?.payload?.data.newResult.data.result || []}
query={widget.query}
ref={lineChartRef}
/>
</span>
);
}
WidgetGraphComponent.defaultProps = {
yAxisUnit: undefined,
setLayout: undefined,
onDragSelect: undefined,
onClickHandler: undefined,
};
export default WidgetGraphComponent;

View File

@@ -0,0 +1,134 @@
import { Skeleton } from 'antd';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import getChartData from 'lib/getChartData';
import isEmpty from 'lodash-es/isEmpty';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { memo, useMemo, useState } from 'react';
import { useInView } from 'react-intersection-observer';
import { useDispatch, useSelector } from 'react-redux';
import { UpdateTimeInterval } from 'store/actions';
import { AppState } from 'store/reducers';
import { GlobalReducer } from 'types/reducer/globalTime';
import EmptyWidget from '../EmptyWidget';
import { MenuItemKeys } from '../WidgetHeader/contants';
import { GridCardGraphProps } from './types';
import WidgetGraphComponent from './WidgetGraphComponent';
function GridCardGraph({
widget,
name,
onClickHandler,
headerMenuList = [MenuItemKeys.View],
isQueryEnabled,
threshold,
}: GridCardGraphProps): JSX.Element {
const dispatch = useDispatch();
const [errorMessage, setErrorMessage] = useState<string>();
const onDragSelect = (start: number, end: number): void => {
const startTimestamp = Math.trunc(start);
const endTimestamp = Math.trunc(end);
if (startTimestamp !== endTimestamp) {
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
}
};
const { ref: graphRef, inView: isGraphVisible } = useInView({
threshold: 0,
triggerOnce: true,
initialInView: false,
});
const { selectedDashboard } = useDashboard();
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const updatedQuery = useStepInterval(widget?.query);
const isEmptyWidget =
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
const queryResponse = useGetQueryRange(
{
selectedTime: widget?.timePreferance,
graphType: widget?.panelTypes,
query: updatedQuery,
globalSelectedInterval,
variables: getDashboardVariables(selectedDashboard?.data.variables),
},
{
queryKey: [
maxTime,
minTime,
globalSelectedInterval,
selectedDashboard?.data?.variables,
widget?.query,
widget?.panelTypes,
widget.timePreferance,
],
keepPreviousData: true,
enabled: isGraphVisible && !isEmptyWidget && isQueryEnabled,
refetchOnMount: false,
onError: (error) => {
setErrorMessage(error.message);
},
},
);
const chartData = useMemo(
() =>
getChartData({
queryData: [
{
queryData: queryResponse?.data?.payload?.data?.result || [],
},
],
createDataset: undefined,
isWarningLimit: true,
}),
[queryResponse],
);
const isEmptyLayout = widget?.id === PANEL_TYPES.EMPTY_WIDGET;
if (queryResponse.isLoading) {
return <Skeleton />;
}
return (
<span ref={graphRef}>
<WidgetGraphComponent
widget={widget}
queryResponse={queryResponse}
errorMessage={errorMessage}
data={chartData.data}
isWarning={chartData.isWarning}
name={name}
onDragSelect={onDragSelect}
threshold={threshold}
headerMenuList={headerMenuList}
onClickHandler={onClickHandler}
/>
{isEmptyLayout && <EmptyWidget />}
</span>
);
}
GridCardGraph.defaultProps = {
onDragSelect: undefined,
onClickHandler: undefined,
isQueryEnabled: true,
threshold: undefined,
headerMenuList: [MenuItemKeys.View],
};
export default memo(GridCardGraph);

View File

@@ -1,15 +1,11 @@
import { ChartData } from 'chart.js';
import { GraphOnClickHandler, ToggleGraphProps } from 'components/Graph/types';
import { Dispatch, MutableRefObject, ReactNode, SetStateAction } from 'react';
import { Layout } from 'react-grid-layout';
import { MutableRefObject, ReactNode } from 'react';
import { UseQueryResult } from 'react-query';
import { DeleteWidgetProps } from 'store/actions/dashboard/deleteWidget';
import AppActions from 'types/actions';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { LayoutProps } from '..';
import { MenuItemKeys } from '../WidgetHeader/contants';
import { LegendEntryProps } from './FullView/types';
@@ -18,15 +14,7 @@ export interface GraphVisibilityLegendEntryProps {
legendEntry: LegendEntryProps[];
}
export interface DispatchProps {
deleteWidget: ({
widgetId,
}: DeleteWidgetProps) => (dispatch: Dispatch<AppActions>) => void;
}
export interface WidgetGraphComponentProps extends DispatchProps {
enableModel: boolean;
enableWidgetHeader: boolean;
export interface WidgetGraphComponentProps {
widget: Widgets;
queryResponse: UseQueryResult<
SuccessResponse<MetricRangePayloadProps> | ErrorResponse
@@ -34,21 +22,16 @@ export interface WidgetGraphComponentProps extends DispatchProps {
errorMessage: string | undefined;
data: ChartData;
name: string;
yAxisUnit?: string;
layout?: Layout[];
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
onDragSelect?: (start: number, end: number) => void;
onClickHandler?: GraphOnClickHandler;
threshold?: ReactNode;
headerMenuList: MenuItemKeys[];
isWarning: boolean;
}
export interface GridCardGraphProps {
widget: Widgets;
name: string;
yAxisUnit: string | undefined;
layout?: Layout[];
setLayout?: Dispatch<SetStateAction<LayoutProps[]>>;
onDragSelect?: (start: number, end: number) => void;
onClickHandler?: GraphOnClickHandler;
threshold?: ReactNode;

View File

@@ -0,0 +1,141 @@
import { PlusOutlined, SaveFilled } from '@ant-design/icons';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard';
import useComponentPermission from 'hooks/useComponentPermission';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useNotifications } from 'hooks/useNotifications';
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
import AppReducer from 'types/reducer/app';
import { headerMenuList } from './config';
import GridCard from './GridCard';
import {
Button,
ButtonContainer,
Card,
CardContainer,
ReactGridLayout,
} from './styles';
import { GraphLayoutProps } from './types';
function GraphLayout({
onAddPanelHandler,
widgets,
}: GraphLayoutProps): JSX.Element {
const {
selectedDashboard,
layouts,
setLayouts,
setSelectedDashboard,
} = useDashboard();
const { t } = useTranslation(['dashboard']);
const { featureResponse, role } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const isDarkMode = useIsDarkMode();
const updateDashboardMutation = useUpdateDashboard();
const { notifications } = useNotifications();
const [saveLayoutPermission, addPanelPermission] = useComponentPermission(
['save_layout', 'add_panel'],
role,
);
const onSaveHandler = (): void => {
if (!selectedDashboard) return;
const updatedDashboard: Dashboard = {
...selectedDashboard,
data: {
...selectedDashboard.data,
layout: layouts.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET),
},
uuid: selectedDashboard.uuid,
};
updateDashboardMutation.mutate(updatedDashboard, {
onSuccess: (updatedDashboard) => {
if (updatedDashboard.payload) {
if (updatedDashboard.payload.data.layout)
setLayouts(updatedDashboard.payload.data.layout);
setSelectedDashboard(updatedDashboard.payload);
}
notifications.success({
message: t('dashboard:layout_saved_successfully'),
});
featureResponse.refetch();
},
onError: () => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
},
});
};
return (
<>
<ButtonContainer>
{saveLayoutPermission && (
<Button
loading={updateDashboardMutation.isLoading}
onClick={onSaveHandler}
icon={<SaveFilled />}
disabled={updateDashboardMutation.isLoading}
>
{t('dashboard:save_layout')}
</Button>
)}
{addPanelPermission && (
<Button onClick={onAddPanelHandler} icon={<PlusOutlined />}>
{t('dashboard:add_panel')}
</Button>
)}
</ButtonContainer>
<ReactGridLayout
cols={12}
rowHeight={100}
autoSize
width={100}
isDraggable={addPanelPermission}
isDroppable={addPanelPermission}
isResizable={addPanelPermission}
allowOverlap={false}
onLayoutChange={setLayouts}
draggableHandle=".drag-handle"
layout={layouts}
>
{layouts.map((layout) => {
const { i: id } = layout;
const currentWidget = (widgets || [])?.find((e) => e.id === id);
return (
<CardContainer isDarkMode={isDarkMode} key={id} data-grid={layout}>
<Card $panelType={currentWidget?.panelTypes || PANEL_TYPES.TIME_SERIES}>
<GridCard
widget={currentWidget || ({ id } as Widgets)}
name={currentWidget?.id || ''}
headerMenuList={headerMenuList}
/>
</Card>
</CardContainer>
);
})}
</ReactGridLayout>
</>
);
}
export default GraphLayout;

View File

@@ -1,9 +1,14 @@
import { themeColors } from 'constants/theme';
import { limit } from 'lib/getChartData';
import { CSSProperties } from 'react';
const positionCss: CSSProperties['position'] = 'absolute';
export const spinnerStyles = { position: positionCss, right: '0.5rem' };
export const spinnerStyles = {
position: positionCss,
top: '0',
right: '0',
};
export const tooltipStyles = {
fontSize: '1rem',
top: '0.313rem',
@@ -21,3 +26,5 @@ export const overlayStyles: CSSProperties = {
justifyContent: 'center',
position: 'absolute',
};
export const WARNING_MESSAGE = `Too many timeseries in the result. UI has restricted to showing the top ${limit}. Please check the query if this is needed and contact support@signoz.io if you need to show >${limit} timeseries in the panel`;

View File

@@ -5,10 +5,12 @@ import {
EditFilled,
ExclamationCircleOutlined,
FullscreenOutlined,
WarningOutlined,
} from '@ant-design/icons';
import { Dropdown, MenuProps, Tooltip, Typography } from 'antd';
import Spinner from 'components/Spinner';
import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import history from 'lib/history';
@@ -20,12 +22,14 @@ import { ErrorResponse, SuccessResponse } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import AppReducer from 'types/reducer/app';
import { popupContainer } from 'utils/selectPopupContainer';
import {
errorTooltipPosition,
overlayStyles,
spinnerStyles,
tooltipStyles,
WARNING_MESSAGE,
} from './config';
import { MENUITEM_KEYS_VS_LABELS, MenuItemKeys } from './contants';
import {
@@ -51,6 +55,7 @@ interface IWidgetHeaderProps {
errorMessage: string | undefined;
threshold?: ReactNode;
headerMenuList?: MenuItemKeys[];
isWarning: boolean;
}
function WidgetHeader({
@@ -64,7 +69,8 @@ function WidgetHeader({
errorMessage,
threshold,
headerMenuList,
}: IWidgetHeaderProps): JSX.Element {
isWarning,
}: IWidgetHeaderProps): JSX.Element | null {
const [localHover, setLocalHover] = useState(false);
const [isOpen, setIsOpen] = useState<boolean>(false);
@@ -125,7 +131,7 @@ function WidgetHeader({
icon: <FullscreenOutlined />,
label: MENUITEM_KEYS_VS_LABELS[MenuItemKeys.View],
isVisible: headerMenuList?.includes(MenuItemKeys.View) || false,
disabled: queryResponse.isLoading,
disabled: queryResponse.isFetching,
},
{
key: MenuItemKeys.Edit,
@@ -157,7 +163,7 @@ function WidgetHeader({
disabled: false,
},
],
[queryResponse.isLoading, headerMenuList, editWidget, deleteWidget],
[headerMenuList, queryResponse.isFetching, editWidget, deleteWidget],
);
const updatedMenuList = useMemo(() => generateMenuList(actions), [actions]);
@@ -174,9 +180,14 @@ function WidgetHeader({
[updatedMenuList, onMenuItemSelectHandler],
);
if (widget.id === PANEL_TYPES.EMPTY_WIDGET) {
return null;
}
return (
<WidgetHeaderContainer>
<Dropdown
getPopupContainer={popupContainer}
destroyPopupOnHide
open={isOpen}
onOpenChange={setIsOpen}
@@ -200,6 +211,7 @@ function WidgetHeader({
</HeaderContentContainer>
</HeaderContainer>
</Dropdown>
<ThesholdContainer>{threshold}</ThesholdContainer>
{queryResponse.isFetching && !queryResponse.isError && (
<Spinner height="5vh" style={spinnerStyles} />
@@ -209,6 +221,12 @@ function WidgetHeader({
<ExclamationCircleOutlined style={tooltipStyles} />
</Tooltip>
)}
{isWarning && (
<Tooltip title={WARNING_MESSAGE} placement={errorTooltipPosition}>
<WarningOutlined style={tooltipStyles} />
</Tooltip>
)}
</WidgetHeaderContainer>
);
}

View File

@@ -0,0 +1,17 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { MenuItemKeys } from 'container/GridCardLayout/WidgetHeader/contants';
export const headerMenuList = [
MenuItemKeys.View,
MenuItemKeys.Clone,
MenuItemKeys.Delete,
MenuItemKeys.Edit,
];
export const EMPTY_WIDGET_LAYOUT = {
i: PANEL_TYPES.EMPTY_WIDGET,
w: 6,
x: 0,
h: 2,
y: 0,
};

View File

@@ -0,0 +1,35 @@
import { useDashboard } from 'providers/Dashboard/Dashboard';
import { useCallback } from 'react';
import { Layout } from 'react-grid-layout';
import { EMPTY_WIDGET_LAYOUT } from './config';
import GraphLayoutContainer from './GridCardLayout';
function GridGraph(): JSX.Element {
const {
selectedDashboard,
setLayouts,
handleToggleDashboardSlider,
} = useDashboard();
const { data } = selectedDashboard || {};
const { widgets } = data || {};
const onEmptyWidgetHandler = useCallback(() => {
handleToggleDashboardSlider(true);
setLayouts((preLayout: Layout[]) => [
EMPTY_WIDGET_LAYOUT,
...(preLayout || []),
]);
}, [handleToggleDashboardSlider, setLayouts]);
return (
<GraphLayoutContainer
onAddPanelHandler={onEmptyWidgetHandler}
widgets={widgets}
/>
);
}
export default GridGraph;

View File

@@ -0,0 +1,6 @@
import { Widgets } from 'types/api/dashboard/getAll';
export interface GraphLayoutProps {
onAddPanelHandler: VoidFunction;
widgets?: Widgets[];
}

View File

@@ -1,207 +0,0 @@
import { Button, Input } from 'antd';
import { CheckboxChangeEvent } from 'antd/es/checkbox';
import { ResizeTable } from 'components/ResizeTable';
import { Events } from 'constants/events';
import { useNotifications } from 'hooks/useNotifications';
import isEqual from 'lodash-es/isEqual';
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { eventEmitter } from 'utils/getEventEmitter';
import { getGraphVisibilityStateOnDataChange } from '../utils';
import {
FilterTableAndSaveContainer,
FilterTableContainer,
SaveCancelButtonContainer,
SaveContainer,
} from './styles';
import { getGraphManagerTableColumns } from './TableRender/GraphManagerColumns';
import { ExtendedChartDataset, GraphManagerProps } from './types';
import {
getDefaultTableDataSet,
saveLegendEntriesToLocalStorage,
} from './utils';
function GraphManager({
data,
name,
yAxisUnit,
onToggleModelHandler,
}: GraphManagerProps): JSX.Element {
const {
graphVisibilityStates: localstoredVisibilityStates,
legendEntry,
} = useMemo(
() =>
getGraphVisibilityStateOnDataChange({
data,
isExpandedName: false,
name,
}),
[data, name],
);
const [graphVisibilityState, setGraphVisibilityState] = useState<boolean[]>(
localstoredVisibilityStates,
);
const [tableDataSet, setTableDataSet] = useState<ExtendedChartDataset[]>(
getDefaultTableDataSet(data),
);
const { notifications } = useNotifications();
// useEffect for updating graph visibility state on data change
useEffect(() => {
const newGraphVisibilityStates = Array<boolean>(data.datasets.length).fill(
true,
);
data.datasets.forEach((dataset, i) => {
const index = legendEntry.findIndex(
(entry) => entry.label === dataset.label,
);
if (index !== -1) {
newGraphVisibilityStates[i] = legendEntry[index].show;
}
});
eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, {
name,
graphVisibilityStates: newGraphVisibilityStates,
});
setGraphVisibilityState(newGraphVisibilityStates);
}, [data, name, legendEntry]);
// useEffect for listening to events event graph legend is clicked
useEffect(() => {
const eventListener = eventEmitter.on(
Events.UPDATE_GRAPH_MANAGER_TABLE,
(data) => {
if (data.name === name) {
const newGraphVisibilityStates = graphVisibilityState;
newGraphVisibilityStates[data.index] = !newGraphVisibilityStates[
data.index
];
eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, {
name,
graphVisibilityStates: newGraphVisibilityStates,
});
setGraphVisibilityState([...newGraphVisibilityStates]);
}
},
);
return (): void => {
eventListener.off(Events.UPDATE_GRAPH_MANAGER_TABLE);
};
}, [graphVisibilityState, name]);
const checkBoxOnChangeHandler = useCallback(
(e: CheckboxChangeEvent, index: number): void => {
graphVisibilityState[index] = e.target.checked;
setGraphVisibilityState([...graphVisibilityState]);
eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, {
name,
graphVisibilityStates: [...graphVisibilityState],
});
},
[graphVisibilityState, name],
);
const labelClickedHandler = useCallback(
(labelIndex: number): void => {
const newGraphVisibilityStates = Array<boolean>(data.datasets.length).fill(
false,
);
newGraphVisibilityStates[labelIndex] = true;
setGraphVisibilityState([...newGraphVisibilityStates]);
eventEmitter.emit(Events.UPDATE_GRAPH_VISIBILITY_STATE, {
name,
graphVisibilityStates: newGraphVisibilityStates,
});
},
[data.datasets.length, name],
);
const columns = useMemo(
() =>
getGraphManagerTableColumns({
data,
checkBoxOnChangeHandler,
graphVisibilityState,
labelClickedHandler,
yAxisUnit,
}),
[
checkBoxOnChangeHandler,
data,
graphVisibilityState,
labelClickedHandler,
yAxisUnit,
],
);
const filterHandler = useCallback(
(event: React.ChangeEvent<HTMLInputElement>): void => {
const value = event.target.value.toString().toLowerCase();
const updatedDataSet = tableDataSet.map((item) => {
if (item.label?.toLocaleLowerCase().includes(value)) {
return { ...item, show: true };
}
return { ...item, show: false };
});
setTableDataSet(updatedDataSet);
},
[tableDataSet],
);
const saveHandler = useCallback((): void => {
saveLegendEntriesToLocalStorage({
data,
graphVisibilityState,
name,
});
notifications.success({
message: 'The updated graphs & legends are saved',
});
if (onToggleModelHandler) {
onToggleModelHandler();
}
}, [data, graphVisibilityState, name, notifications, onToggleModelHandler]);
const dataSource = tableDataSet.filter((item) => item.show);
return (
<FilterTableAndSaveContainer>
<FilterTableContainer>
<Input onChange={filterHandler} placeholder="Filter Series" />
<ResizeTable
columns={columns}
dataSource={dataSource}
rowKey="index"
pagination={false}
scroll={{ y: 240 }}
/>
</FilterTableContainer>
<SaveContainer>
<SaveCancelButtonContainer>
<Button type="default" onClick={onToggleModelHandler}>
Cancel
</Button>
</SaveCancelButtonContainer>
<SaveCancelButtonContainer>
<Button onClick={saveHandler} type="primary">
Save
</Button>
</SaveCancelButtonContainer>
</SaveContainer>
</FilterTableAndSaveContainer>
);
}
GraphManager.defaultProps = {
graphVisibilityStateHandler: undefined,
};
export default memo(
GraphManager,
(prevProps, nextProps) =>
isEqual(prevProps.data, nextProps.data) && prevProps.name === nextProps.name,
);

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