mirror of
https://github.com/SigNoz/signoz.git
synced 2026-07-03 13:20:34 +01:00
Compare commits
12 Commits
feat/trace
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94a5e568fd | ||
|
|
25cd3fc705 | ||
|
|
5c3fdb64ed | ||
|
|
bac68a6707 | ||
|
|
766a05e24b | ||
|
|
1a84503f4b | ||
|
|
9454427b47 | ||
|
|
124d76068c | ||
|
|
704b8bf917 | ||
|
|
3e6339019e | ||
|
|
372d304a6f | ||
|
|
dbb4eb9574 |
5
.github/CODEOWNERS
vendored
5
.github/CODEOWNERS
vendored
@@ -109,10 +109,7 @@ go.mod @therealpandey
|
||||
/pkg/modules/role/ @therealpandey
|
||||
/pkg/types/coretypes/ @therealpandey @vikrantgupta25
|
||||
|
||||
/frontend/src/hooks/useAuthZ/ @H4ad
|
||||
/frontend/src/components/GuardAuthZ/ @H4ad
|
||||
/frontend/src/components/AuthZTooltip/ @H4ad
|
||||
/frontend/src/components/createGuardedRoute/ @H4ad
|
||||
/frontend/src/lib/authz/ @H4ad
|
||||
/frontend/src/container/RolesSettings/ @H4ad
|
||||
/frontend/src/components/RolesSelect/ @H4ad
|
||||
/frontend/src/pages/MembersSettings/ @H4ad
|
||||
|
||||
7
.github/workflows/build-enterprise.yaml
vendored
7
.github/workflows/build-enterprise.yaml
vendored
@@ -61,13 +61,6 @@ jobs:
|
||||
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
|
||||
echo 'VITE_TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
|
||||
echo 'VITE_TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
|
||||
echo 'VITE_POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
|
||||
echo 'VITE_PYLON_APP_ID="${{ secrets.PYLON_APP_ID }}"' >> frontend/.env
|
||||
echo 'VITE_APPCUES_APP_ID="${{ secrets.APPCUES_APP_ID }}"' >> frontend/.env
|
||||
echo 'VITE_PYLON_IDENTITY_SECRET="${{ secrets.PYLON_IDENTITY_SECRET }}"' >> frontend/.env
|
||||
echo 'VITE_DOCS_BASE_URL="https://signoz.io"' >> frontend/.env
|
||||
echo 'VITE_ENVIRONMENT="production"' >> frontend/.env
|
||||
echo 'VITE_VERSION="${{ steps.build-info.outputs.version }}"' >> frontend/.env
|
||||
|
||||
6
.github/workflows/build-staging.yaml
vendored
6
.github/workflows/build-staging.yaml
vendored
@@ -67,12 +67,6 @@ jobs:
|
||||
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
|
||||
echo 'VITE_SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
|
||||
echo 'VITE_TUNNEL_URL="${{ secrets.NP_TUNNEL_URL }}"' >> frontend/.env
|
||||
echo 'VITE_TUNNEL_DOMAIN="${{ secrets.NP_TUNNEL_DOMAIN }}"' >> frontend/.env
|
||||
echo 'VITE_PYLON_APP_ID="${{ secrets.NP_PYLON_APP_ID }}"' >> frontend/.env
|
||||
echo 'VITE_APPCUES_APP_ID="${{ secrets.NP_APPCUES_APP_ID }}"' >> frontend/.env
|
||||
echo 'VITE_PYLON_IDENTITY_SECRET="${{ secrets.NP_PYLON_IDENTITY_SECRET }}"' >> frontend/.env
|
||||
echo 'VITE_DOCS_BASE_URL="https://staging.signoz.io"' >> frontend/.env
|
||||
echo 'VITE_ENVIRONMENT="staging"' >> frontend/.env
|
||||
echo 'VITE_VERSION="${{ steps.build-info.outputs.version }}"' >> frontend/.env
|
||||
|
||||
7
.github/workflows/gor-signoz.yaml
vendored
7
.github/workflows/gor-signoz.yaml
vendored
@@ -27,13 +27,6 @@ jobs:
|
||||
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> .env
|
||||
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> .env
|
||||
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> .env
|
||||
echo 'VITE_SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> .env
|
||||
echo 'VITE_TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> .env
|
||||
echo 'VITE_TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> .env
|
||||
echo 'VITE_POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> .env
|
||||
echo 'VITE_PYLON_APP_ID="${{ secrets.PYLON_APP_ID }}"' >> .env
|
||||
echo 'VITE_APPCUES_APP_ID="${{ secrets.APPCUES_APP_ID }}"' >> .env
|
||||
echo 'VITE_PYLON_IDENTITY_SECRET="${{ secrets.PYLON_IDENTITY_SECRET }}"' >> .env
|
||||
echo 'VITE_DOCS_BASE_URL="https://signoz.io"' >> .env
|
||||
echo 'VITE_ENVIRONMENT="production"' >> .env
|
||||
echo 'VITE_VERSION="${{ github.ref_name }}"' >> .env
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const permissionsTypePath = "frontend/src/hooks/useAuthZ/permissions.type.ts"
|
||||
const permissionsTypePath = "frontend/src/lib/authz/hooks/useAuthZ/permissions.type.ts"
|
||||
|
||||
var permissionsTypeTemplate = template.Must(template.New("permissions").Parse(
|
||||
`// AUTO GENERATED FILE - DO NOT EDIT - GENERATED BY cmd/enterprise/*.go generate authz
|
||||
|
||||
@@ -3071,6 +3071,10 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/DashboardtypesListedDashboardForUserV2'
|
||||
type: array
|
||||
reservedKeywords:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/TagtypesGettableTag'
|
||||
@@ -3082,6 +3086,7 @@ components:
|
||||
- dashboards
|
||||
- total
|
||||
- tags
|
||||
- reservedKeywords
|
||||
type: object
|
||||
DashboardtypesListableDashboardV2:
|
||||
properties:
|
||||
@@ -3089,6 +3094,10 @@ components:
|
||||
items:
|
||||
$ref: '#/components/schemas/DashboardtypesListedDashboardV2'
|
||||
type: array
|
||||
reservedKeywords:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
tags:
|
||||
items:
|
||||
$ref: '#/components/schemas/TagtypesGettableTag'
|
||||
@@ -3100,6 +3109,7 @@ components:
|
||||
- dashboards
|
||||
- total
|
||||
- tags
|
||||
- reservedKeywords
|
||||
type: object
|
||||
DashboardtypesListableDashboardView:
|
||||
properties:
|
||||
|
||||
@@ -1,105 +1,116 @@
|
||||
# Configuring Over Local
|
||||
1. Docker
|
||||
1. Without Docker
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="../docs/readme-assets/signoz-hero-dark.png" width="700">
|
||||
<source media="(prefers-color-scheme: light)" srcset="../docs/readme-assets/signoz-hero-light.png" width="700">
|
||||
<img alt="SigNoz - Observability on Your Terms" src="../docs/readme-assets/signoz-hero-light.png" width="700">
|
||||
</picture>
|
||||
</p>
|
||||
|
||||
## With Docker
|
||||
<p align="center">
|
||||
<a href="https://github.com/SigNoz/signoz/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/SigNoz/signoz"></a>
|
||||
<a href="https://signoz.io/slack"><img alt="Slack community" src="https://img.shields.io/badge/slack-community-4A154B?logo=slack&logoColor=white"></a>
|
||||
</p>
|
||||
|
||||
**Building image**
|
||||
# SigNoz Frontend
|
||||
|
||||
``docker compose up`
|
||||
/ This will also run
|
||||
React-based web interface for [SigNoz](https://signoz.io), the open-source observability platform.
|
||||
|
||||
or
|
||||
`docker build . -t tagname`
|
||||
## Tech Stack
|
||||
|
||||
**Tag to remote url- Introduce versioning later on**
|
||||
- **Framework:** React 18 + TypeScript
|
||||
- **Build:** Vite
|
||||
- **State:** React Query, Zustand, Redux Toolkit (legacy)
|
||||
- **Styling:** CSS Modules, Ant Design (legacy)
|
||||
- **Charts:** uPlot
|
||||
- **Testing:** Jest
|
||||
|
||||
```
|
||||
docker tag signoz/frontend:latest 7296823551/signoz:latest
|
||||
## Local Development Setup
|
||||
|
||||
1. Run SigNoz backend locally — see [Self-Host Docs](https://signoz.io/docs/install/self-host/)
|
||||
|
||||
2. Configure environment:
|
||||
```bash
|
||||
cp example.env .env
|
||||
```
|
||||
|
||||
Key variables in `.env`:
|
||||
```bash
|
||||
# Backend API endpoint (required)
|
||||
VITE_FRONTEND_API_ENDPOINT="http://localhost:8080"
|
||||
|
||||
# Enable bundle analyzer (optional)
|
||||
BUNDLE_ANALYSER="true"
|
||||
```
|
||||
|
||||
3. Install and run:
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
```
|
||||
docker compose up
|
||||
Opens [http://localhost:3301](http://localhost:3301).
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Without Docker
|
||||
Follow the steps below
|
||||
Output in `build/` folder.
|
||||
|
||||
1. ```git clone https://github.com/SigNoz/signoz.git && cd signoz/frontend```
|
||||
1. change baseURL to ```<test environment URL>``` in file ```src/constants/env.ts```
|
||||
## Bundle Size Analysis
|
||||
|
||||
1. ```pnpm install```
|
||||
1. ```pnpm dev```
|
||||
Set in `.env`:
|
||||
```bash
|
||||
BUNDLE_ANALYSER="true"
|
||||
```
|
||||
|
||||
```Note: Please ping us in #contributing channel in our slack community and we will DM you with <test environment URL>```
|
||||
Then run build:
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
# Getting Started with Create React App
|
||||
Opens bundle analyzer visualization automatically.
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
## Testing
|
||||
|
||||
## Available Scripts
|
||||
```bash
|
||||
# Unit tests
|
||||
pnpm test
|
||||
|
||||
In the project directory, you can run:
|
||||
# Type checking
|
||||
pnpm tsgo --noEmit
|
||||
```
|
||||
|
||||
### `pnpm start`
|
||||
## Linting
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3301](http://localhost:3301) to view it in the browser.
|
||||
```bash
|
||||
# Run all linters (oxlint + stylelint)
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
## Project Structure
|
||||
|
||||
### `pnpm test`
|
||||
```
|
||||
src/
|
||||
├── api/ # API clients and react-query hooks
|
||||
├── components/ # Shared UI components
|
||||
├── container/ # Page-level containers
|
||||
├── hooks/ # Custom React hooks
|
||||
├── pages/ # Route pages
|
||||
├── providers/ # React context providers
|
||||
├── store/ # Redux store
|
||||
└── types/ # TypeScript definitions
|
||||
```
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
## Contributing
|
||||
|
||||
### `pnpm build`
|
||||
See [CONTRIBUTING.md](../CONTRIBUTING.md) in the root repo.
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `pnpm eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `pnpm build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
Questions? Join our [Slack community](https://signoz.io/slack).
|
||||
|
||||
@@ -1,8 +1,38 @@
|
||||
NODE_ENV="development"
|
||||
BUNDLE_ANALYSER="true"
|
||||
VITE_FRONTEND_API_ENDPOINT="http://localhost:8080"
|
||||
VITE_PYLON_APP_ID="pylon-app-id"
|
||||
VITE_APPCUES_APP_ID="appcess-app-id"
|
||||
VITE_PYLON_IDENTITY_SECRET="pylon-identity-secret"
|
||||
|
||||
CI="1"
|
||||
|
||||
# API
|
||||
VITE_BASE_PATH=""
|
||||
VITE_FRONTEND_API_ENDPOINT="http://localhost:8080"
|
||||
VITE_WEBSOCKET_API_ENDPOINT=""
|
||||
|
||||
# Pylon
|
||||
VITE_PYLON_ENABLED="false"
|
||||
VITE_PYLON_APP_ID=""
|
||||
VITE_PYLON_IDENTITY_SECRET=""
|
||||
|
||||
# Appcues
|
||||
VITE_APPCUES_ENABLED="false"
|
||||
VITE_APPCUES_APP_ID=""
|
||||
|
||||
# PostHog
|
||||
VITE_POSTHOG_ENABLED="false"
|
||||
VITE_POSTHOG_API_HOST=""
|
||||
VITE_POSTHOG_KEY=""
|
||||
VITE_POSTHOG_UI_HOST=""
|
||||
|
||||
# Sentry
|
||||
VITE_SENTRY_ENABLED="false"
|
||||
VITE_SENTRY_AUTH_TOKEN=""
|
||||
VITE_SENTRY_ORG=""
|
||||
VITE_SENTRY_PROJECT_ID=""
|
||||
VITE_SENTRY_TUNNEL=""
|
||||
VITE_SENTRY_DSN=""
|
||||
|
||||
# Docs
|
||||
VITE_DOCS_BASE_URL="https://signoz.io"
|
||||
|
||||
# Build info
|
||||
VITE_ENVIRONMENT="development"
|
||||
VITE_VERSION=""
|
||||
|
||||
@@ -111,11 +111,10 @@
|
||||
<div id="root"></div>
|
||||
|
||||
<script>
|
||||
var PYLON_APP_ID = '<%- PYLON_APP_ID %>';
|
||||
var pylonSettings =
|
||||
((window.signozBootData || {}).settings || {}).pylon || {};
|
||||
var pylonEnabled = pylonSettings.enabled !== false;
|
||||
if (PYLON_APP_ID && pylonEnabled) {
|
||||
var pylonEnabled = pylonSettings.enabled === true;
|
||||
if (pylonSettings.appId && pylonEnabled) {
|
||||
(function () {
|
||||
var e = window;
|
||||
var t = document;
|
||||
@@ -133,7 +132,7 @@
|
||||
e.setAttribute('async', 'true');
|
||||
e.setAttribute(
|
||||
'src',
|
||||
'https://widget.usepylon.com/widget/' + PYLON_APP_ID,
|
||||
'https://widget.usepylon.com/widget/' + pylonSettings.appId,
|
||||
);
|
||||
var n = t.getElementsByTagName('script')[0];
|
||||
n.parentNode.insertBefore(e, n);
|
||||
@@ -150,15 +149,14 @@
|
||||
window.AppcuesSettings = { enableURLDetection: true };
|
||||
</script>
|
||||
<script>
|
||||
var APPCUES_APP_ID = '<%- APPCUES_APP_ID %>';
|
||||
var appcuesSettings =
|
||||
((window.signozBootData || {}).settings || {}).appcues || {};
|
||||
var appcuesEnabled = appcuesSettings.enabled !== false;
|
||||
if (APPCUES_APP_ID && appcuesEnabled) {
|
||||
var appcuesEnabled = appcuesSettings.enabled === true;
|
||||
if (appcuesSettings.appId && appcuesEnabled) {
|
||||
(function (d, t) {
|
||||
var a = d.createElement(t);
|
||||
a.async = 1;
|
||||
a.src = '//fast.appcues.com/' + APPCUES_APP_ID + '.js';
|
||||
a.src = '//fast.appcues.com/' + appcuesSettings.appId + '.js';
|
||||
var s = d.getElementsByTagName(t)[0];
|
||||
s.parentNode.insertBefore(a, s);
|
||||
})(document, 'script');
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
"project": ["src/**/*.ts", "src/**/*.tsx"],
|
||||
"ignore": ["src/api/generated/**/*.ts", "src/typings/*.ts"],
|
||||
"ignoreDependencies": [
|
||||
"http-proxy-middleware",
|
||||
"@typescript/native-preview"
|
||||
]
|
||||
}
|
||||
@@ -79,7 +79,6 @@
|
||||
"event-source-polyfill": "1.0.31",
|
||||
"eventemitter3": "5.0.1",
|
||||
"history": "4.10.1",
|
||||
"http-proxy-middleware": "4.1.1",
|
||||
"http-status-codes": "2.3.0",
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
|
||||
22
frontend/pnpm-lock.yaml
generated
22
frontend/pnpm-lock.yaml
generated
@@ -164,9 +164,6 @@ importers:
|
||||
history:
|
||||
specifier: 4.10.1
|
||||
version: 4.10.1
|
||||
http-proxy-middleware:
|
||||
specifier: 4.1.1
|
||||
version: 4.1.1
|
||||
http-status-codes:
|
||||
specifier: 2.3.0
|
||||
version: 2.3.0
|
||||
@@ -5461,10 +5458,6 @@ packages:
|
||||
resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
http-proxy-middleware@4.1.1:
|
||||
resolution: {integrity: sha512-KX5ZofGXLFXqFAkQoOWZ+rTtaLTut7m0gyL+QzJrdejtIZ+F4bPPDoe7reISg2+v0CAz5OfVwEJEhty7X+e57g==}
|
||||
engines: {node: ^22.15.0 || ^24.0.0 || >=26.0.0}
|
||||
|
||||
http-status-codes@2.3.0:
|
||||
resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==}
|
||||
|
||||
@@ -5472,9 +5465,6 @@ packages:
|
||||
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
httpxy@0.5.3:
|
||||
resolution: {integrity: sha512-SMS9V6Sn7VWaS11lYhoAr0ceoaiolTWf4jYdJn0NJhCdKMu9R2H9Fh0LBDWBHQF6HRLI1PmaePYsjanSpE5PEw==}
|
||||
|
||||
human-signals@2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
@@ -14515,16 +14505,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
http-proxy-middleware@4.1.1:
|
||||
dependencies:
|
||||
debug: 4.3.4(supports-color@5.5.0)
|
||||
httpxy: 0.5.3
|
||||
is-glob: 4.0.3
|
||||
is-plain-obj: 4.1.0
|
||||
micromatch: 4.0.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
http-status-codes@2.3.0: {}
|
||||
|
||||
https-proxy-agent@5.0.1:
|
||||
@@ -14534,8 +14514,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
httpxy@0.5.3: {}
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
human-signals@8.0.1: {}
|
||||
|
||||
@@ -292,10 +292,10 @@ function App(): JSX.Element {
|
||||
isChatSupportEnabled &&
|
||||
!showAddCreditCardModal &&
|
||||
(isCloudUser || isEnterpriseSelfHostedUser) &&
|
||||
(window.signozBootData?.settings?.pylon.enabled ?? true)
|
||||
window.signozBootData?.settings?.pylon?.enabled
|
||||
) {
|
||||
const email = user.email || '';
|
||||
const secret = process.env.PYLON_IDENTITY_SECRET || '';
|
||||
const secret = window.signozBootData?.settings?.pylon?.identitySecret || '';
|
||||
let emailHash = '';
|
||||
|
||||
if (email && secret) {
|
||||
@@ -304,7 +304,7 @@ function App(): JSX.Element {
|
||||
|
||||
window.pylon = {
|
||||
chat_settings: {
|
||||
app_id: process.env.PYLON_APP_ID,
|
||||
app_id: window.signozBootData?.settings?.pylon?.appId,
|
||||
email: user.email,
|
||||
name: user.displayName || user.email,
|
||||
email_hash: emailHash,
|
||||
@@ -335,22 +335,23 @@ function App(): JSX.Element {
|
||||
useEffect(() => {
|
||||
if (isCloudUser || isEnterpriseSelfHostedUser) {
|
||||
if (
|
||||
(window.signozBootData?.settings?.posthog.enabled ?? true) &&
|
||||
process.env.POSTHOG_KEY
|
||||
window.signozBootData?.settings?.posthog?.enabled &&
|
||||
window.signozBootData?.settings?.posthog?.key
|
||||
) {
|
||||
posthog.init(process.env.POSTHOG_KEY, {
|
||||
api_host: 'https://us.i.posthog.com',
|
||||
posthog.init(window.signozBootData.settings.posthog.key, {
|
||||
api_host: window.signozBootData.settings.posthog.apiHost,
|
||||
ui_host: window.signozBootData.settings.posthog.uiHost,
|
||||
person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!isSentryInitialized &&
|
||||
(window.signozBootData?.settings?.sentry.enabled ?? true)
|
||||
window.signozBootData?.settings?.sentry?.enabled
|
||||
) {
|
||||
Sentry.init({
|
||||
dsn: process.env.SENTRY_DSN,
|
||||
tunnel: process.env.TUNNEL_URL,
|
||||
dsn: window.signozBootData.settings.sentry.dsn,
|
||||
tunnel: window.signozBootData.settings.sentry.tunnel,
|
||||
environment: process.env.ENVIRONMENT,
|
||||
release: process.env.VERSION,
|
||||
integrations: [
|
||||
|
||||
@@ -5031,6 +5031,10 @@ export interface DashboardtypesListableDashboardForUserV2DTO {
|
||||
* @type array
|
||||
*/
|
||||
dashboards: DashboardtypesListedDashboardForUserV2DTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
reservedKeywords: string[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
@@ -5098,6 +5102,10 @@ export interface DashboardtypesListableDashboardV2DTO {
|
||||
* @type array
|
||||
*/
|
||||
dashboards: DashboardtypesListedDashboardV2DTO[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
reservedKeywords: string[];
|
||||
/**
|
||||
* @type array
|
||||
*/
|
||||
|
||||
@@ -2,8 +2,8 @@ import { Controller, useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import { SACreatePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import { SACreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/ui/dialog';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
|
||||
import CreateServiceAccountModal from '../CreateServiceAccountModal';
|
||||
|
||||
jest.mock('components/AuthZTooltip/AuthZTooltip', () => ({
|
||||
jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
children,
|
||||
|
||||
@@ -4,11 +4,11 @@ import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { DatePicker } from 'antd';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import {
|
||||
APIKeyCreatePermission,
|
||||
buildSAAttachPermission,
|
||||
} from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { disabledDate } from '../utils';
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import { buildSADeletePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import { buildSADeletePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { DialogWrapper } from '@signozhq/ui/dialog';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
|
||||
@@ -7,12 +7,12 @@ import { Input } from '@signozhq/ui/input';
|
||||
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
|
||||
import { DatePicker } from 'antd';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import {
|
||||
buildAPIKeyDeletePermission,
|
||||
buildAPIKeyUpdatePermission,
|
||||
buildSADetachPermission,
|
||||
} from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { disabledDate, formatLastObservedAt } from '../utils';
|
||||
|
||||
@@ -16,8 +16,8 @@ import type {
|
||||
import { AxiosError } from 'axios';
|
||||
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
||||
import dayjs from 'dayjs';
|
||||
import { buildAPIKeyUpdatePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { buildAPIKeyUpdatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
|
||||
@@ -4,13 +4,13 @@ import { Button } from '@signozhq/ui/button';
|
||||
import { Skeleton, Table, Tooltip } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import {
|
||||
APIKeyCreatePermission,
|
||||
buildAPIKeyDeletePermission,
|
||||
buildSAAttachPermission,
|
||||
buildSADetachPermission,
|
||||
} from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import dayjs from 'dayjs';
|
||||
import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs';
|
||||
|
||||
@@ -5,11 +5,11 @@ import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import RolesSelect from 'components/RolesSelect';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import { ServiceAccountRow } from 'container/ServiceAccountsSettings/utils';
|
||||
import { buildSAUpdatePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { buildSAUpdatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import {
|
||||
buildAPIKeyDeletePermission,
|
||||
buildSADetachPermission,
|
||||
} from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { DialogWrapper } from '@signozhq/ui/dialog';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||
import PermissionDeniedCallout from 'components/PermissionDeniedCallout/PermissionDeniedCallout';
|
||||
import PermissionDeniedCallout from 'lib/authz/components/PermissionDeniedCallout/PermissionDeniedCallout';
|
||||
import { useRoles } from 'components/RolesSelect';
|
||||
import { SA_QUERY_PARAMS } from 'container/ServiceAccountsSettings/constants';
|
||||
import {
|
||||
@@ -35,8 +35,8 @@ import {
|
||||
buildSADeletePermission,
|
||||
buildSAReadPermission,
|
||||
buildSAUpdatePermission,
|
||||
} from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import {
|
||||
parseAsBoolean,
|
||||
parseAsInteger,
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
import APIError from 'types/api/error';
|
||||
import { toAPIError } from 'utils/errorUtils';
|
||||
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import AddKeyModal from './AddKeyModal';
|
||||
import DeleteAccountModal from './DeleteAccountModal';
|
||||
import KeysTab from './KeysTab';
|
||||
|
||||
@@ -6,7 +6,7 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import EditKeyModal from '../EditKeyModal';
|
||||
|
||||
jest.mock('components/AuthZTooltip/AuthZTooltip', () => ({
|
||||
jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
children,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import KeysTab from '../KeysTab';
|
||||
|
||||
jest.mock('components/AuthZTooltip/AuthZTooltip', () => ({
|
||||
jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
|
||||
__esModule: true,
|
||||
default: ({
|
||||
children,
|
||||
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
setupAuthzAdmin,
|
||||
setupAuthzDeny,
|
||||
setupAuthzDenyAll,
|
||||
} from 'tests/authz-test-utils';
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
import {
|
||||
APIKeyListPermission,
|
||||
buildSADeletePermission,
|
||||
} from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
|
||||
import ServiceAccountDrawer from '../ServiceAccountDrawer';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { listRolesSuccessResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { setupAuthzAdmin } from 'tests/authz-test-utils';
|
||||
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import ServiceAccountDrawer from '../ServiceAccountDrawer';
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@ import { useCallback, useMemo, useRef } from 'react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { Skeleton } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import Uplot from 'components/Uplot';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import TimeSeries from 'container/DashboardContainer/visualization/charts/TimeSeries/TimeSeries';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8s/constants';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import {
|
||||
@@ -13,13 +14,13 @@ import {
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import { useMultiIntersectionObserver } from 'hooks/useMultiIntersectionObserver';
|
||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { AlignedData, Options } from 'uplot';
|
||||
|
||||
import { useMultiIntersectionObserver } from 'hooks/useMultiIntersectionObserver';
|
||||
import { buildEntityMetricsChartConfig } from './configBuilder';
|
||||
|
||||
import { useEntityMetrics } from './hooks';
|
||||
import { isKeyNotFoundError } from '../utils';
|
||||
@@ -70,7 +71,7 @@ function EntityMetrics<T>({
|
||||
{ threshold: 0.1 },
|
||||
);
|
||||
|
||||
const { queries, chartData, queryPayloads } = useEntityMetrics({
|
||||
const { queries, chartData, tableData, queryPayloads } = useEntityMetrics({
|
||||
queryKey,
|
||||
timeRange,
|
||||
entity,
|
||||
@@ -80,16 +81,10 @@ function EntityMetrics<T>({
|
||||
});
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { timezone } = useTimezone();
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
const dimensions = useResizeObserver(graphRef);
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const legendScrollPositionRef = useRef<{
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}>({
|
||||
scrollTop: 0,
|
||||
scrollLeft: 0,
|
||||
});
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number): void => {
|
||||
@@ -101,43 +96,39 @@ function EntityMetrics<T>({
|
||||
[handleTimeChange],
|
||||
);
|
||||
|
||||
const options = useMemo(
|
||||
const configs = useMemo(
|
||||
() =>
|
||||
queries.map(({ data }, idx) => {
|
||||
const panelType = queryPayloads[idx]?.graphType;
|
||||
if (panelType === PANEL_TYPES.TABLE) {
|
||||
return null;
|
||||
}
|
||||
return getUPlotChartOptions({
|
||||
apiResponse: data?.payload,
|
||||
const widgetTitle = entityWidgetInfo[idx].title
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-');
|
||||
return buildEntityMetricsChartConfig({
|
||||
id: `${category}-${widgetTitle}`,
|
||||
isDarkMode,
|
||||
dimensions,
|
||||
currentQuery,
|
||||
onDragSelect,
|
||||
apiResponse: data?.payload,
|
||||
timezone,
|
||||
yAxisUnit: entityWidgetInfo[idx].yAxisUnit,
|
||||
softMax: null,
|
||||
softMin: null,
|
||||
minTimeScale: timeRange.startTime,
|
||||
maxTimeScale: timeRange.endTime,
|
||||
onDragSelect,
|
||||
query: currentQuery,
|
||||
legendScrollPosition: legendScrollPositionRef.current,
|
||||
setLegendScrollPosition: (position: {
|
||||
scrollTop: number;
|
||||
scrollLeft: number;
|
||||
}): void => {
|
||||
legendScrollPositionRef.current = position;
|
||||
},
|
||||
});
|
||||
}),
|
||||
[
|
||||
queries,
|
||||
queryPayloads,
|
||||
category,
|
||||
isDarkMode,
|
||||
dimensions,
|
||||
currentQuery,
|
||||
onDragSelect,
|
||||
timezone,
|
||||
entityWidgetInfo,
|
||||
timeRange.startTime,
|
||||
timeRange.endTime,
|
||||
onDragSelect,
|
||||
currentQuery,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -170,14 +161,22 @@ function EntityMetrics<T>({
|
||||
>
|
||||
{panelType === PANEL_TYPES.TABLE ? (
|
||||
<MetricsTable
|
||||
rows={chartData[idx]?.[0]?.rows ?? []}
|
||||
columns={chartData[idx]?.[0]?.columns ?? []}
|
||||
rows={tableData[idx]?.[0]?.rows ?? []}
|
||||
columns={tableData[idx]?.[0]?.columns ?? []}
|
||||
/>
|
||||
) : (
|
||||
<Uplot
|
||||
options={options[idx] as Options}
|
||||
data={chartData[idx] as AlignedData}
|
||||
/>
|
||||
configs[idx] &&
|
||||
chartData[idx] && (
|
||||
<TimeSeries
|
||||
config={configs[idx]}
|
||||
data={chartData[idx]}
|
||||
legendConfig={{ position: LegendPosition.BOTTOM }}
|
||||
width={dimensions.width}
|
||||
height={dimensions.height}
|
||||
timezone={timezone}
|
||||
yAxisUnit={entityWidgetInfo[idx].yAxisUnit}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { InfraMonitoringEntity } from 'container/InfraMonitoringK8s/constants';
|
||||
import { Time } from 'container/TopNav/DateTimeSelectionV2/types';
|
||||
import * as appContextHooks from 'providers/App/App';
|
||||
import { LicenseEvent } from 'types/api/licensesV3/getActive';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import EntityMetrics from '../EntityMetrics';
|
||||
import { useEntityMetrics } from '../hooks';
|
||||
@@ -15,12 +16,15 @@ const mockUseEntityMetrics = useEntityMetrics as jest.MockedFunction<
|
||||
typeof useEntityMetrics
|
||||
>;
|
||||
|
||||
jest.mock('lib/uPlotLib/getUplotChartOptions', () => ({
|
||||
getUPlotChartOptions: jest.fn().mockReturnValue({}),
|
||||
jest.mock('../configBuilder', () => ({
|
||||
buildEntityMetricsChartConfig: jest.fn().mockReturnValue({
|
||||
getId: jest.fn().mockReturnValue('mock-id'),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('lib/uPlotLib/utils/getUplotChartData', () => ({
|
||||
getUPlotChartData: jest.fn().mockReturnValue([]),
|
||||
jest.mock('lib/uPlotV2/utils/dataUtils', () => ({
|
||||
prepareChartData: jest.fn().mockReturnValue([]),
|
||||
hasSingleVisiblePoint: jest.fn().mockReturnValue(false),
|
||||
}));
|
||||
|
||||
jest.mock('container/TopNav/DateTimeSelectionV2', () => ({
|
||||
@@ -30,9 +34,20 @@ jest.mock('container/TopNav/DateTimeSelectionV2', () => ({
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('components/Uplot', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => <div data-testid="uplot-chart">Uplot Chart</div>,
|
||||
jest.mock(
|
||||
'container/DashboardContainer/visualization/charts/TimeSeries/TimeSeries',
|
||||
() => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => (
|
||||
<div data-testid="uplot-chart">TimeSeries Chart</div>
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
jest.mock('providers/Timezone', () => ({
|
||||
useTimezone: (): { timezone: { value: string } } => ({
|
||||
timezone: { value: 'UTC' },
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../MetricsTable', () => ({
|
||||
@@ -294,20 +309,28 @@ const renderEntityMetrics = (overrides = {}): any => {
|
||||
);
|
||||
};
|
||||
|
||||
const mockChartData = [
|
||||
[], // time_series chart data (uplot handles empty array)
|
||||
const mockChartData: (uPlot.AlignedData | null)[] = [
|
||||
[
|
||||
[1705315200, 1705318800],
|
||||
[42.5, 43.2],
|
||||
], // time_series chart data (AlignedData)
|
||||
null, // table uses tableData
|
||||
];
|
||||
|
||||
const mockTableData: (import('../utils').MetricsTableData[] | null)[] = [
|
||||
null, // time_series uses chartData
|
||||
[
|
||||
{
|
||||
rows: [
|
||||
{ data: { timestamp: '2024-01-15T10:00:00Z', value: '1024' } },
|
||||
{ data: { timestamp: '2024-01-15T10:01:00Z', value: '1028' } },
|
||||
{ timestamp: '2024-01-15T10:00:00Z', value: '1024' },
|
||||
{ timestamp: '2024-01-15T10:01:00Z', value: '1028' },
|
||||
],
|
||||
columns: [
|
||||
{ key: 'timestamp', label: 'Timestamp', isValueColumn: false },
|
||||
{ key: 'value', label: 'Value', isValueColumn: true },
|
||||
],
|
||||
},
|
||||
], // table chart data
|
||||
], // table data
|
||||
];
|
||||
|
||||
const mockQueryPayloads = [
|
||||
@@ -321,6 +344,7 @@ describe('EntityMetrics', () => {
|
||||
mockUseEntityMetrics.mockReturnValue({
|
||||
queries: mockQueries as any,
|
||||
chartData: mockChartData,
|
||||
tableData: mockTableData,
|
||||
queryPayloads: mockQueryPayloads as any,
|
||||
});
|
||||
mockUseQuery.mockReturnValue({
|
||||
@@ -351,7 +375,8 @@ describe('EntityMetrics', () => {
|
||||
it('renders loading state when fetching metrics', () => {
|
||||
mockUseEntityMetrics.mockReturnValue({
|
||||
queries: mockLoadingQueries as any,
|
||||
chartData: [[], []],
|
||||
chartData: [null, null],
|
||||
tableData: [null, null],
|
||||
queryPayloads: mockQueryPayloads as any,
|
||||
});
|
||||
renderEntityMetrics();
|
||||
@@ -362,7 +387,8 @@ describe('EntityMetrics', () => {
|
||||
it('renders error state when query fails', () => {
|
||||
mockUseEntityMetrics.mockReturnValue({
|
||||
queries: mockErrorQueries as any,
|
||||
chartData: [[], []],
|
||||
chartData: [null, null],
|
||||
tableData: [null, null],
|
||||
queryPayloads: mockQueryPayloads as any,
|
||||
});
|
||||
renderEntityMetrics();
|
||||
@@ -373,8 +399,9 @@ describe('EntityMetrics', () => {
|
||||
it('renders empty state when no metrics data', () => {
|
||||
mockUseEntityMetrics.mockReturnValue({
|
||||
queries: mockEmptyQueries as any,
|
||||
chartData: [
|
||||
[],
|
||||
chartData: [[[]], null],
|
||||
tableData: [
|
||||
null,
|
||||
[
|
||||
{
|
||||
rows: [],
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { getLegend } from 'lib/dashboard/getQueryResults';
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
import {
|
||||
DrawStyle,
|
||||
FillMode,
|
||||
LineInterpolation,
|
||||
LineStyle,
|
||||
SelectionPreferencesSource,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { hasSingleVisiblePoint } from 'lib/uPlotV2/utils/dataUtils';
|
||||
import { get } from 'lodash-es';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
export interface EntityMetricsChartConfigProps {
|
||||
id: string;
|
||||
isDarkMode: boolean;
|
||||
currentQuery: Query;
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
timezone: Timezone;
|
||||
yAxisUnit: string;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
}
|
||||
|
||||
export function buildEntityMetricsChartConfig({
|
||||
id,
|
||||
isDarkMode,
|
||||
currentQuery,
|
||||
onDragSelect,
|
||||
apiResponse,
|
||||
timezone,
|
||||
yAxisUnit,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
}: EntityMetricsChartConfigProps): UPlotConfigBuilder {
|
||||
const stepIntervals = get(
|
||||
apiResponse,
|
||||
'data.newResult.meta.stepIntervals',
|
||||
{},
|
||||
) as Record<string, number>;
|
||||
const minStepInterval = Object.keys(stepIntervals).length
|
||||
? Math.min(...Object.values(stepIntervals))
|
||||
: undefined;
|
||||
|
||||
const tzDate = (timestamp: number): Date =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value);
|
||||
|
||||
const builder = new UPlotConfigBuilder({
|
||||
id,
|
||||
onDragSelect,
|
||||
tzDate,
|
||||
selectionPreferencesSource: SelectionPreferencesSource.IN_MEMORY,
|
||||
stepInterval: minStepInterval,
|
||||
});
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
time: true,
|
||||
min: minTimeScale,
|
||||
max: maxTimeScale,
|
||||
});
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'y',
|
||||
time: false,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
show: true,
|
||||
side: 2,
|
||||
isDarkMode,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'y',
|
||||
show: true,
|
||||
side: 3,
|
||||
isDarkMode,
|
||||
yAxisUnit,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
});
|
||||
|
||||
if (!apiResponse?.data?.result) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
apiResponse.data.result.forEach((series) => {
|
||||
const hasSingleValidPoint = hasSingleVisiblePoint(series.values);
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
series.queryName || '',
|
||||
series.legend || '',
|
||||
);
|
||||
|
||||
const label = getLegend(series, currentQuery, baseLabelName);
|
||||
|
||||
builder.addSeries({
|
||||
scaleKey: 'y',
|
||||
drawStyle: hasSingleValidPoint ? DrawStyle.Points : DrawStyle.Line,
|
||||
label,
|
||||
colorMapping: {},
|
||||
spanGaps: true,
|
||||
lineStyle: LineStyle.Solid,
|
||||
lineInterpolation: LineInterpolation.Spline,
|
||||
showPoints: hasSingleValidPoint,
|
||||
pointSize: 5,
|
||||
fillMode: FillMode.None,
|
||||
isDarkMode,
|
||||
metric: series.metric,
|
||||
});
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
@@ -8,13 +8,14 @@ import {
|
||||
GetMetricQueryRange,
|
||||
GetQueryResultsProps,
|
||||
} from 'lib/dashboard/getQueryResults';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { prepareChartData } from 'lib/uPlotV2/utils/dataUtils';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { FeatureKeys } from '../../../../constants/features';
|
||||
import { useAppContext } from '../../../../providers/App/App';
|
||||
import { getMetricsTableData } from './utils';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { getMetricsTableData, MetricsTableData } from './utils';
|
||||
|
||||
export interface UseEntityMetricsParams<T> {
|
||||
queryKey: string;
|
||||
@@ -32,10 +33,8 @@ export interface UseEntityMetricsParams<T> {
|
||||
|
||||
export interface UseEntityMetricsResult {
|
||||
queries: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>[];
|
||||
chartData: (
|
||||
| ReturnType<typeof getUPlotChartData>
|
||||
| ReturnType<typeof getMetricsTableData>
|
||||
)[];
|
||||
chartData: (uPlot.AlignedData | null)[];
|
||||
tableData: (MetricsTableData[] | null)[];
|
||||
queryPayloads: GetQueryResultsProps[];
|
||||
}
|
||||
|
||||
@@ -93,9 +92,22 @@ export function useEntityMetrics<T>({
|
||||
() =>
|
||||
queries.map(({ data }, index) => {
|
||||
const panelType = queryPayloads[index]?.graphType;
|
||||
return panelType === PANEL_TYPES.TABLE
|
||||
? getMetricsTableData(data)
|
||||
: getUPlotChartData(data?.payload);
|
||||
if (panelType === PANEL_TYPES.TABLE) {
|
||||
return null;
|
||||
}
|
||||
return data?.payload ? prepareChartData(data.payload) : null;
|
||||
}),
|
||||
[queries, queryPayloads],
|
||||
);
|
||||
|
||||
const tableData = useMemo(
|
||||
() =>
|
||||
queries.map(({ data }, index) => {
|
||||
const panelType = queryPayloads[index]?.graphType;
|
||||
if (panelType !== PANEL_TYPES.TABLE) {
|
||||
return null;
|
||||
}
|
||||
return getMetricsTableData(data);
|
||||
}),
|
||||
[queries, queryPayloads],
|
||||
);
|
||||
@@ -103,6 +115,7 @@ export function useEntityMetrics<T>({
|
||||
return {
|
||||
queries,
|
||||
chartData,
|
||||
tableData,
|
||||
queryPayloads,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.emptyMeterSearch {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Empty } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
import styles from './EmptyMeterSearch.module.scss';
|
||||
|
||||
interface EmptyMeterSearchProps {
|
||||
hasQueryResult?: boolean;
|
||||
}
|
||||
|
||||
export default function EmptyMeterSearch({
|
||||
hasQueryResult,
|
||||
}: EmptyMeterSearchProps): JSX.Element {
|
||||
return (
|
||||
<div className={styles.emptyMeterSearch}>
|
||||
<Empty
|
||||
description={
|
||||
<Typography.Title level={5}>
|
||||
{hasQueryResult
|
||||
? 'No data'
|
||||
: 'Select a metric and run a query to see the results'}
|
||||
</Typography.Title>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -73,34 +73,6 @@
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.empty-meter-search {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.time-series-view-panel {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
padding: 8px !important;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.time-series-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(
|
||||
auto-fit,
|
||||
minmax(min(100%, calc(50% - 8px)), 1fr)
|
||||
);
|
||||
gap: 16px;
|
||||
width: 100%;
|
||||
height: fit-content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,22 +85,6 @@
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
.meter-time-series-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.builder-units-filter {
|
||||
padding: 0 8px;
|
||||
margin-bottom: 0px !important;
|
||||
|
||||
.builder-units-filter-label {
|
||||
margin-bottom: 0px !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dashboards-and-alerts-popover-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
|
||||
@@ -35,7 +35,6 @@ function Explorer(): JSX.Element {
|
||||
handleRunQuery,
|
||||
stagedQuery,
|
||||
updateAllQueriesOperators,
|
||||
handleSetQueryData,
|
||||
currentQuery,
|
||||
} = useQueryBuilder();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
@@ -67,15 +66,6 @@ function Explorer(): JSX.Element {
|
||||
[updateAllQueriesOperators],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
handleSetQueryData(0, {
|
||||
...initialQueryMeterWithType.builder.queryData[0],
|
||||
source: 'meter',
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const exportDefaultQuery = useMemo(
|
||||
() =>
|
||||
updateAllQueriesOperators(
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
.loadingMeter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
height: 240px;
|
||||
padding: var(--spacing-12) 0;
|
||||
}
|
||||
|
||||
.loadingMeterContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.loadingGif {
|
||||
height: 72px;
|
||||
margin-left: calc(-1 * var(--spacing-12));
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import loadingPlaneUrl from '@/assets/Icons/loading-plane.gif';
|
||||
|
||||
import styles from './MeterLoading.module.scss';
|
||||
|
||||
export default function MeterLoading(): JSX.Element {
|
||||
return (
|
||||
<div className={styles.loadingMeter}>
|
||||
<div className={styles.loadingMeterContent}>
|
||||
<img className={styles.loadingGif} src={loadingPlaneUrl} alt="wait-icon" />
|
||||
<Typography>Retrieving your {DataSource.METRICS}</Typography>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
.meterTimeSeriesContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-5);
|
||||
width: 100%;
|
||||
|
||||
:global(.builder-units-filter) {
|
||||
padding: 0 var(--spacing-4);
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
:global(.builder-units-filter-label) {
|
||||
margin-bottom: 0 !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.timeSeriesContainer {
|
||||
gap: var(--spacing-8);
|
||||
width: 100%;
|
||||
height: 50vh;
|
||||
max-height: 50vh;
|
||||
padding-right: 16px;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.timeSeriesViewPanel {
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
}
|
||||
@@ -1,27 +1,28 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { useMemo, useRef } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { isAxiosError } from 'axios';
|
||||
import QueryCancelledPlaceholder from 'components/QueryCancelledPlaceholder';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { initialQueryMeterWithType, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { MAX_QUERY_RETRIES } from 'constants/reactQuery';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import EmptyMetricsSearch from 'container/MetricsExplorer/Explorer/EmptyMetricsSearch';
|
||||
import BarChart from 'container/DashboardContainer/visualization/charts/BarChart/BarChart';
|
||||
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters/BuilderUnitsFilter';
|
||||
import TimeSeriesView from 'container/TimeSeriesView/TimeSeriesView';
|
||||
import { convertDataValueToMs } from 'container/TimeSeriesView/utils';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useResizeObserver } from 'hooks/useDimensions';
|
||||
import useUrlYAxisUnit from 'hooks/useUrlYAxisUnit';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { LegendPosition } from 'lib/uPlotV2/components/types';
|
||||
import { prepareChartData } from 'lib/uPlotV2/utils/dataUtils';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
import { buildMeterChartConfig } from './configBuilder';
|
||||
import EmptyMeterSearch from './EmptyMeterSearch';
|
||||
import MeterLoading from './MeterLoading';
|
||||
import styles from './TimeSeries.module.scss';
|
||||
import { useTimeSeriesQueries } from './useTimeSeriesQueries';
|
||||
import { useTimeSeriesTimeManagement } from './useTimeSeriesTimeManagement';
|
||||
|
||||
const WIDGET_ID = 'meter-explorer-bar-chart';
|
||||
|
||||
interface TimeSeriesProps {
|
||||
onFetchingStateChange?: (isFetching: boolean) => void;
|
||||
@@ -32,144 +33,124 @@ function TimeSeries({
|
||||
onFetchingStateChange,
|
||||
isCancelled = false,
|
||||
}: TimeSeriesProps): JSX.Element {
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { stagedQuery, currentQuery } = useQueryBuilder();
|
||||
const { yAxisUnit, onUnitChange } = useUrlYAxisUnit('');
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const { timezone } = useTimezone();
|
||||
const containerDimensions = useResizeObserver(graphRef);
|
||||
|
||||
const {
|
||||
selectedTime: globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
} = useSelector<AppState, GlobalReducer>((state) => state.globalTime);
|
||||
|
||||
const isValidToConvertToMs = useMemo(() => {
|
||||
const isValid: boolean[] = [];
|
||||
const { minTimeScale, maxTimeScale, onDragSelect } =
|
||||
useTimeSeriesTimeManagement({
|
||||
globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
});
|
||||
|
||||
currentQuery.builder.queryData.forEach(
|
||||
({ aggregateAttribute, aggregateOperator }) => {
|
||||
const isExistDurationNanoAttribute =
|
||||
aggregateAttribute?.key === 'durationNano' ||
|
||||
aggregateAttribute?.key === 'duration_nano';
|
||||
|
||||
const isCountOperator =
|
||||
aggregateOperator === 'count' || aggregateOperator === 'count_distinct';
|
||||
|
||||
isValid.push(!isCountOperator && isExistDurationNanoAttribute);
|
||||
},
|
||||
);
|
||||
|
||||
return isValid.every(Boolean);
|
||||
}, [currentQuery]);
|
||||
|
||||
const queryPayloads = useMemo(
|
||||
() => [stagedQuery || initialQueryMeterWithType],
|
||||
[stagedQuery],
|
||||
);
|
||||
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const queries = useQueries(
|
||||
queryPayloads.map((payload, index) => ({
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
payload,
|
||||
ENTITY_VERSION_V5,
|
||||
globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
index,
|
||||
],
|
||||
queryFn: ({
|
||||
signal,
|
||||
}: {
|
||||
signal?: AbortSignal;
|
||||
}): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
||||
GetMetricQueryRange(
|
||||
{
|
||||
query: payload,
|
||||
graphType: PANEL_TYPES.BAR,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
params: {
|
||||
dataSource: DataSource.METRICS,
|
||||
},
|
||||
},
|
||||
ENTITY_VERSION_V5,
|
||||
undefined,
|
||||
signal,
|
||||
),
|
||||
enabled: !!payload,
|
||||
retry: (failureCount: number, error: unknown): boolean => {
|
||||
if (isAxiosError(error) && error.code === 'ERR_CANCELED') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let status: number | undefined;
|
||||
|
||||
if (error instanceof APIError) {
|
||||
status = error.getHttpStatusCode();
|
||||
} else if (isAxiosError(error)) {
|
||||
status = error.response?.status;
|
||||
}
|
||||
|
||||
if (status && status >= 400 && status < 500) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return failureCount < MAX_QUERY_RETRIES;
|
||||
},
|
||||
onError: (error: APIError): void => {
|
||||
showErrorModal(error);
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
const isFetching = queries.some((q) => q.isFetching);
|
||||
useEffect(() => {
|
||||
onFetchingStateChange?.(isFetching);
|
||||
}, [isFetching, onFetchingStateChange]);
|
||||
|
||||
const data = useMemo(() => queries.map(({ data }) => data) ?? [], [queries]);
|
||||
|
||||
const responseData = useMemo(
|
||||
() =>
|
||||
data.map((datapoint) =>
|
||||
isValidToConvertToMs ? convertDataValueToMs(datapoint) : datapoint,
|
||||
),
|
||||
[data, isValidToConvertToMs],
|
||||
);
|
||||
const { responseData, isLoading, isError } = useTimeSeriesQueries({
|
||||
stagedQuery,
|
||||
currentQuery,
|
||||
globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
onFetchingStateChange,
|
||||
});
|
||||
|
||||
const hasMetricSelected = useMemo(
|
||||
() => currentQuery.builder.queryData.some((q) => q.aggregateAttribute?.key),
|
||||
[currentQuery],
|
||||
);
|
||||
|
||||
const chartsData = useMemo(() => {
|
||||
return responseData.map((response, index) => {
|
||||
const apiResponse = response?.payload;
|
||||
|
||||
const config = buildMeterChartConfig({
|
||||
id: `${WIDGET_ID}-${index}`,
|
||||
isDarkMode,
|
||||
currentQuery,
|
||||
onDragSelect,
|
||||
apiResponse,
|
||||
timezone,
|
||||
yAxisUnit: yAxisUnit || 'short',
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
});
|
||||
|
||||
const chartData = apiResponse ? prepareChartData(apiResponse) : [];
|
||||
|
||||
return {
|
||||
config,
|
||||
chartData,
|
||||
hasData: chartData.length > 0 && chartData[0]?.length > 0,
|
||||
};
|
||||
});
|
||||
}, [
|
||||
responseData,
|
||||
currentQuery,
|
||||
yAxisUnit,
|
||||
isDarkMode,
|
||||
onDragSelect,
|
||||
timezone,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
]);
|
||||
|
||||
const hasAnyData = chartsData.some((chart) => chart.hasData);
|
||||
|
||||
return (
|
||||
<div className="meter-time-series-container">
|
||||
<div className={styles.meterTimeSeriesContainer}>
|
||||
<BuilderUnitsFilter onChange={onUnitChange} yAxisUnit={yAxisUnit} />
|
||||
<div className="time-series-container">
|
||||
{!hasMetricSelected && <EmptyMetricsSearch />}
|
||||
<div className={styles.timeSeriesContainer} ref={graphRef}>
|
||||
{!hasMetricSelected && <EmptyMeterSearch />}
|
||||
{isCancelled && hasMetricSelected && (
|
||||
<QueryCancelledPlaceholder subText='Click "Run Query" to load metrics.' />
|
||||
)}
|
||||
{isLoading && hasMetricSelected && !isCancelled && <MeterLoading />}
|
||||
{!isCancelled &&
|
||||
hasMetricSelected &&
|
||||
responseData.map((datapoint, index) => (
|
||||
<div
|
||||
className="time-series-view-panel"
|
||||
// eslint-disable-next-line react/no-array-index-key
|
||||
key={index}
|
||||
>
|
||||
<TimeSeriesView
|
||||
isFilterApplied={false}
|
||||
isError={queries[index].isError}
|
||||
isLoading={queries[index].isLoading}
|
||||
data={datapoint}
|
||||
dataSource={DataSource.METRICS}
|
||||
yAxisUnit={yAxisUnit}
|
||||
panelType={PANEL_TYPES.BAR}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
!isLoading &&
|
||||
!isError &&
|
||||
!hasAnyData && (
|
||||
<EmptyMeterSearch hasQueryResult={responseData[0] !== undefined} />
|
||||
)}
|
||||
{!isCancelled &&
|
||||
hasMetricSelected &&
|
||||
!isLoading &&
|
||||
!isError &&
|
||||
containerDimensions.width > 0 &&
|
||||
containerDimensions.height > 0 &&
|
||||
chartsData.map(
|
||||
(chart, index) =>
|
||||
chart.hasData && (
|
||||
<div
|
||||
className={styles.timeSeriesViewPanel}
|
||||
// oxlint-disable-next-line react/no-array-index-key -- query responses have no stable ID
|
||||
key={`${WIDGET_ID}-${index}`}
|
||||
>
|
||||
<BarChart
|
||||
config={chart.config}
|
||||
legendConfig={{
|
||||
position: LegendPosition.BOTTOM,
|
||||
}}
|
||||
data={chart.chartData as uPlot.AlignedData}
|
||||
width={containerDimensions.width}
|
||||
height={containerDimensions.height}
|
||||
isStackedBarChart
|
||||
yAxisUnit={yAxisUnit || 'short'}
|
||||
timezone={timezone}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
117
frontend/src/container/MeterExplorer/Explorer/configBuilder.ts
Normal file
117
frontend/src/container/MeterExplorer/Explorer/configBuilder.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { getInitialStackedBands } from 'container/DashboardContainer/visualization/charts/utils/stackSeriesUtils';
|
||||
import { getLegend } from 'lib/dashboard/getQueryResults';
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
import {
|
||||
DrawStyle,
|
||||
SelectionPreferencesSource,
|
||||
} from 'lib/uPlotV2/config/types';
|
||||
import { UPlotConfigBuilder } from 'lib/uPlotV2/config/UPlotConfigBuilder';
|
||||
import { get } from 'lodash-es';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import uPlot from 'uplot';
|
||||
|
||||
export interface MeterChartConfigProps {
|
||||
id: string;
|
||||
isDarkMode: boolean;
|
||||
currentQuery: Query;
|
||||
onDragSelect: (startTime: number, endTime: number) => void;
|
||||
apiResponse?: MetricRangePayloadProps;
|
||||
timezone: Timezone;
|
||||
yAxisUnit: string;
|
||||
minTimeScale?: number;
|
||||
maxTimeScale?: number;
|
||||
}
|
||||
|
||||
export function buildMeterChartConfig({
|
||||
id,
|
||||
isDarkMode,
|
||||
currentQuery,
|
||||
onDragSelect,
|
||||
apiResponse,
|
||||
timezone,
|
||||
yAxisUnit,
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
}: MeterChartConfigProps): UPlotConfigBuilder {
|
||||
const stepIntervals = get(
|
||||
apiResponse,
|
||||
'data.newResult.meta.stepIntervals',
|
||||
{},
|
||||
) as Record<string, number>;
|
||||
const minStepInterval = Object.keys(stepIntervals).length
|
||||
? Math.min(...Object.values(stepIntervals))
|
||||
: undefined;
|
||||
|
||||
const tzDate = (timestamp: number): Date =>
|
||||
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value);
|
||||
|
||||
const builder = new UPlotConfigBuilder({
|
||||
id,
|
||||
onDragSelect,
|
||||
tzDate,
|
||||
selectionPreferencesSource: SelectionPreferencesSource.IN_MEMORY,
|
||||
stepInterval: minStepInterval,
|
||||
});
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'x',
|
||||
time: true,
|
||||
min: minTimeScale,
|
||||
max: maxTimeScale,
|
||||
});
|
||||
|
||||
builder.addScale({
|
||||
scaleKey: 'y',
|
||||
time: false,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'x',
|
||||
show: true,
|
||||
side: 2,
|
||||
isDarkMode,
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
});
|
||||
|
||||
builder.addAxis({
|
||||
scaleKey: 'y',
|
||||
show: true,
|
||||
side: 3,
|
||||
isDarkMode,
|
||||
yAxisUnit,
|
||||
panelType: PANEL_TYPES.BAR,
|
||||
});
|
||||
|
||||
if (!apiResponse?.data?.result) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
const seriesCount = (apiResponse.data.result.length ?? 0) + 1;
|
||||
builder.setBands(getInitialStackedBands(seriesCount));
|
||||
|
||||
apiResponse.data.result.forEach((series) => {
|
||||
const baseLabelName = getLabelName(
|
||||
series.metric,
|
||||
series.queryName || '',
|
||||
series.legend || '',
|
||||
);
|
||||
|
||||
const label = getLegend(series, currentQuery, baseLabelName);
|
||||
const currentStepInterval = get(stepIntervals, series.queryName, undefined);
|
||||
|
||||
builder.addSeries({
|
||||
scaleKey: 'y',
|
||||
drawStyle: DrawStyle.Bar,
|
||||
label,
|
||||
colorMapping: {},
|
||||
isDarkMode,
|
||||
stepInterval: currentStepInterval,
|
||||
metric: series.metric,
|
||||
});
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useQueries } from 'react-query';
|
||||
import { isAxiosError } from 'axios';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { initialQueryMeterWithType, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { MAX_QUERY_RETRIES } from 'constants/reactQuery';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/types';
|
||||
import { convertDataValueToMs } from 'container/TimeSeriesView/utils';
|
||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import APIError from 'types/api/error';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
interface UseTimeSeriesQueriesProps {
|
||||
stagedQuery: Query | null;
|
||||
currentQuery: Query;
|
||||
globalSelectedTime: Time | CustomTimeType;
|
||||
maxTime: number;
|
||||
minTime: number;
|
||||
onFetchingStateChange?: (isFetching: boolean) => void;
|
||||
}
|
||||
|
||||
interface UseTimeSeriesQueriesResult {
|
||||
responseData: (SuccessResponse<MetricRangePayloadProps> | undefined)[];
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
}
|
||||
|
||||
export function useTimeSeriesQueries({
|
||||
stagedQuery,
|
||||
currentQuery,
|
||||
globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
onFetchingStateChange,
|
||||
}: UseTimeSeriesQueriesProps): UseTimeSeriesQueriesResult {
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const isValidToConvertToMs = useMemo(() => {
|
||||
const isValid: boolean[] = [];
|
||||
|
||||
currentQuery.builder.queryData.forEach(
|
||||
({ aggregateAttribute, aggregateOperator }) => {
|
||||
const isExistDurationNanoAttribute =
|
||||
aggregateAttribute?.key === 'durationNano' ||
|
||||
aggregateAttribute?.key === 'duration_nano';
|
||||
|
||||
const isCountOperator =
|
||||
aggregateOperator === 'count' || aggregateOperator === 'count_distinct';
|
||||
|
||||
isValid.push(!isCountOperator && isExistDurationNanoAttribute);
|
||||
},
|
||||
);
|
||||
|
||||
return isValid.every(Boolean);
|
||||
}, [currentQuery]);
|
||||
|
||||
const queryPayloads = useMemo(
|
||||
() => [stagedQuery || initialQueryMeterWithType],
|
||||
[stagedQuery],
|
||||
);
|
||||
|
||||
const queries = useQueries(
|
||||
queryPayloads.map((payload, index) => ({
|
||||
queryKey: [
|
||||
REACT_QUERY_KEY.GET_QUERY_RANGE,
|
||||
payload,
|
||||
ENTITY_VERSION_V5,
|
||||
globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
index,
|
||||
],
|
||||
queryFn: ({
|
||||
signal,
|
||||
}: {
|
||||
signal?: AbortSignal;
|
||||
}): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
||||
GetMetricQueryRange(
|
||||
{
|
||||
query: payload,
|
||||
graphType: PANEL_TYPES.BAR,
|
||||
selectedTime: 'GLOBAL_TIME',
|
||||
globalSelectedInterval: globalSelectedTime,
|
||||
params: {
|
||||
dataSource: DataSource.METRICS,
|
||||
},
|
||||
},
|
||||
ENTITY_VERSION_V5,
|
||||
undefined,
|
||||
signal,
|
||||
),
|
||||
enabled: !!payload,
|
||||
retry: (failureCount: number, error: unknown): boolean => {
|
||||
if (isAxiosError(error) && error.code === 'ERR_CANCELED') {
|
||||
return false;
|
||||
}
|
||||
|
||||
let status: number | undefined;
|
||||
|
||||
if (error instanceof APIError) {
|
||||
status = error.getHttpStatusCode();
|
||||
} else if (isAxiosError(error)) {
|
||||
status = error.response?.status;
|
||||
}
|
||||
|
||||
if (status && status >= 400 && status < 500) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return failureCount < MAX_QUERY_RETRIES;
|
||||
},
|
||||
onError: (error: APIError): void => {
|
||||
showErrorModal(error);
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
const isFetching = queries.some((q) => q.isFetching);
|
||||
useEffect(() => {
|
||||
onFetchingStateChange?.(isFetching);
|
||||
}, [isFetching, onFetchingStateChange]);
|
||||
|
||||
const responseData = useMemo(() => {
|
||||
const data = queries.map(({ data }) => data) ?? [];
|
||||
return data.map((datapoint) =>
|
||||
isValidToConvertToMs ? convertDataValueToMs(datapoint) : datapoint,
|
||||
);
|
||||
}, [queries, isValidToConvertToMs]);
|
||||
|
||||
const isLoading = queries.some((q) => q.isLoading);
|
||||
const isError = queries.some((q) => q.isError);
|
||||
|
||||
return {
|
||||
responseData,
|
||||
isLoading,
|
||||
isError,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
} from 'container/TopNav/DateTimeSelectionV2/types';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import history from 'lib/history';
|
||||
import { UpdateTimeInterval } from 'store/actions';
|
||||
import { getTimeRange } from 'utils/getTimeRange';
|
||||
|
||||
interface UseTimeSeriesTimeManagementProps {
|
||||
globalSelectedTime: Time | CustomTimeType;
|
||||
maxTime: number;
|
||||
minTime: number;
|
||||
}
|
||||
|
||||
interface UseTimeSeriesTimeManagementResult {
|
||||
minTimeScale: number | undefined;
|
||||
maxTimeScale: number | undefined;
|
||||
onDragSelect: (start: number, end: number) => void;
|
||||
}
|
||||
|
||||
export function useTimeSeriesTimeManagement({
|
||||
globalSelectedTime,
|
||||
maxTime,
|
||||
minTime,
|
||||
}: UseTimeSeriesTimeManagementProps): UseTimeSeriesTimeManagementResult {
|
||||
const dispatch = useDispatch();
|
||||
const urlQuery = useUrlQuery();
|
||||
const location = useLocation();
|
||||
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
const [maxTimeScale, setMaxTimeScale] = useState<number>();
|
||||
|
||||
useEffect((): void => {
|
||||
const { startTime, endTime } = getTimeRange();
|
||||
setMinTimeScale(startTime);
|
||||
setMaxTimeScale(endTime);
|
||||
}, [maxTime, minTime, globalSelectedTime]);
|
||||
|
||||
const onDragSelect = useCallback(
|
||||
(start: number, end: number): void => {
|
||||
const startTimestamp = Math.trunc(start);
|
||||
const endTimestamp = Math.trunc(end);
|
||||
|
||||
if (startTimestamp !== endTimestamp) {
|
||||
dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp]));
|
||||
}
|
||||
|
||||
const { maxTime, minTime } = GetMinMax('custom', [
|
||||
startTimestamp,
|
||||
endTimestamp,
|
||||
]);
|
||||
|
||||
urlQuery.set(QueryParams.startTime, minTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, maxTime.toString());
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
const generatedUrl = `${location.pathname}?${urlQuery.toString()}`;
|
||||
history.push(generatedUrl);
|
||||
},
|
||||
[dispatch, location.pathname, urlQuery],
|
||||
);
|
||||
|
||||
const handleBackNavigation = useCallback((): void => {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
const startTime = searchParams.get(QueryParams.startTime);
|
||||
const endTime = searchParams.get(QueryParams.endTime);
|
||||
const relativeTime = searchParams.get(
|
||||
QueryParams.relativeTime,
|
||||
) as CustomTimeType;
|
||||
|
||||
if (relativeTime) {
|
||||
dispatch(UpdateTimeInterval(relativeTime));
|
||||
} else if (startTime && endTime && startTime !== endTime) {
|
||||
dispatch(
|
||||
UpdateTimeInterval('custom', [
|
||||
parseInt(getTimeString(startTime), 10),
|
||||
parseInt(getTimeString(endTime), 10),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('popstate', handleBackNavigation);
|
||||
return (): void => {
|
||||
window.removeEventListener('popstate', handleBackNavigation);
|
||||
};
|
||||
}, [handleBackNavigation]);
|
||||
|
||||
return {
|
||||
minTimeScale,
|
||||
maxTimeScale,
|
||||
onDragSelect,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
.autoRefresh {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
.select {
|
||||
min-width: 96px;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { RefreshCw } from '@signozhq/icons';
|
||||
import { SelectSimple } from '@signozhq/ui/select';
|
||||
import { refreshIntervalOptions } from 'container/TopNav/AutoRefreshV2/constants';
|
||||
|
||||
import styles from './AutoRefresh.module.scss';
|
||||
|
||||
const REFRESH_ITEMS = refreshIntervalOptions.map((option) => ({
|
||||
value: option.key,
|
||||
label: option.key === 'off' ? 'Off' : option.label,
|
||||
}));
|
||||
|
||||
interface AutoRefreshProps {
|
||||
value: string;
|
||||
disabled?: boolean;
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
// Interval selector for the public dashboard. Self-contained (no Redux global
|
||||
// time); the container advances its own time window on each tick.
|
||||
function AutoRefresh({
|
||||
value,
|
||||
disabled = false,
|
||||
onChange,
|
||||
}: AutoRefreshProps): JSX.Element {
|
||||
return (
|
||||
<div className={styles.autoRefresh}>
|
||||
<RefreshCw size={14} className={styles.icon} />
|
||||
<SelectSimple
|
||||
className={styles.select}
|
||||
testId="public-dashboard-auto-refresh"
|
||||
items={REFRESH_ITEMS}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
withPortal={false}
|
||||
onChange={(next): void => onChange(next as string)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AutoRefresh;
|
||||
@@ -1,10 +1,12 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import RGL, { WidthProvider } from 'react-grid-layout';
|
||||
import { useInterval } from 'react-use';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import cx from 'classnames';
|
||||
import { PANEL_GROUP_TYPES, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { themeColors } from 'constants/theme';
|
||||
import { Card, CardContainer } from 'container/GridCardLayout/styles';
|
||||
import { refreshIntervalOptions } from 'container/TopNav/AutoRefreshV2/constants';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constants';
|
||||
import {
|
||||
@@ -14,12 +16,14 @@ import {
|
||||
import dayjs from 'dayjs';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { PublicDashboardDataProps } from 'types/api/dashboard/public/get';
|
||||
|
||||
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
|
||||
|
||||
import AutoRefresh from './AutoRefresh';
|
||||
import Panel from './Panel';
|
||||
|
||||
import './PublicDashboardContainer.styles.scss';
|
||||
@@ -121,14 +125,34 @@ function PublicDashboardContainer({
|
||||
const { maxTime, minTime } = GetMinMax(interval);
|
||||
|
||||
setSelectedTimeRange({
|
||||
startTime: Math.floor(minTime / 1000000000),
|
||||
endTime: Math.floor(maxTime / 1000000000),
|
||||
startTime: Math.floor(minTime / NANO_SECOND_MULTIPLIER / 1000),
|
||||
endTime: Math.floor(maxTime / NANO_SECOND_MULTIPLIER / 1000),
|
||||
});
|
||||
}
|
||||
|
||||
setSelectedTimeRangeLabel(interval as string);
|
||||
};
|
||||
|
||||
const [refreshIntervalKey, setRefreshIntervalKey] = useState<string>('off');
|
||||
|
||||
// Auto-refresh only makes sense for a rolling relative range, not a fixed
|
||||
// custom window — pause it (and disable the control) when 'custom' is picked.
|
||||
const isAutoRefreshPaused = selectedTimeRangeLabel === 'custom';
|
||||
|
||||
const refreshIntervalMs = useMemo(
|
||||
() =>
|
||||
refreshIntervalOptions.find((option) => option.key === refreshIntervalKey)
|
||||
?.value || 0,
|
||||
[refreshIntervalKey],
|
||||
);
|
||||
|
||||
// Re-run the existing time-change handler with the current relative range so
|
||||
// the rolling window advances — no need to duplicate the GetMinMax logic.
|
||||
useInterval(
|
||||
() => handleTimeChange(selectedTimeRangeLabel as Time),
|
||||
isAutoRefreshPaused || refreshIntervalMs === 0 ? null : refreshIntervalMs,
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="public-dashboard-container">
|
||||
<div className="public-dashboard-header">
|
||||
@@ -148,6 +172,11 @@ function PublicDashboardContainer({
|
||||
|
||||
{isTimeRangeEnabled && (
|
||||
<div className="public-dashboard-header-right">
|
||||
<AutoRefresh
|
||||
value={refreshIntervalKey}
|
||||
disabled={isAutoRefreshPaused}
|
||||
onChange={setRefreshIntervalKey}
|
||||
/>
|
||||
<div className="datetime-section">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, screen } from 'tests/test-utils';
|
||||
|
||||
import AutoRefresh from '../AutoRefresh';
|
||||
|
||||
describe('Public dashboard AutoRefresh', () => {
|
||||
it('renders the interval selector', () => {
|
||||
render(<AutoRefresh value="off" onChange={jest.fn()} />);
|
||||
expect(
|
||||
screen.getByTestId('public-dashboard-auto-refresh'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('lets the viewer pick a refresh interval', async () => {
|
||||
const onChange = jest.fn();
|
||||
render(<AutoRefresh value="off" onChange={onChange} />);
|
||||
|
||||
await userEvent.click(screen.getByTestId('public-dashboard-auto-refresh'));
|
||||
await userEvent.click(screen.getByText('30 seconds'));
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith('30s');
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import { Input } from '@signozhq/ui/input';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Skeleton } from 'antd';
|
||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||
import PermissionDeniedFullPage from 'components/PermissionDeniedFullPage/PermissionDeniedFullPage';
|
||||
import PermissionDeniedFullPage from 'lib/authz/components/PermissionDeniedFullPage/PermissionDeniedFullPage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useRolesFeatureGate } from 'hooks/useRolesFeatureGate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { defaultFeatureFlags, render, screen } from 'tests/test-utils';
|
||||
import { invalidLicense, mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import {
|
||||
invalidLicense,
|
||||
mockUseAuthZGrantAll,
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import CreateEditRolePage from '../CreateEditRolePage';
|
||||
|
||||
jest.mock('hooks/useAuthZ/useAuthZ');
|
||||
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
|
||||
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { render, screen } from 'tests/test-utils';
|
||||
import { mockUseAuthZDenyAll } from 'tests/authz-test-utils';
|
||||
import { mockUseAuthZDenyAll } from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import CreateEditRolePage from '../CreateEditRolePage';
|
||||
|
||||
jest.mock('hooks/useAuthZ/useAuthZ');
|
||||
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
|
||||
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -3,12 +3,12 @@ import ROUTES from 'constants/routes';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { render, screen, userEvent, waitFor, within } from 'tests/test-utils';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import CreateEditRolePage from '../CreateEditRolePage';
|
||||
|
||||
jest.mock('hooks/useAuthZ/useAuthZ');
|
||||
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
|
||||
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
|
||||
|
||||
const rolesApiBase = '*/api/v1/roles';
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { render, screen } from 'tests/test-utils';
|
||||
import {
|
||||
mockUseAuthZDenyAll,
|
||||
mockUseAuthZGrantByPrefix,
|
||||
} from 'tests/authz-test-utils';
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import CreateEditRolePage from '../CreateEditRolePage';
|
||||
|
||||
jest.mock('hooks/useAuthZ/useAuthZ');
|
||||
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
|
||||
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
|
||||
|
||||
const EDIT_ROLE_ID = 'test-role-123';
|
||||
|
||||
@@ -4,12 +4,12 @@ import { customRoleResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { render, screen, userEvent, waitFor, within } from 'tests/test-utils';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import CreateEditRolePage from '../CreateEditRolePage';
|
||||
|
||||
jest.mock('hooks/useAuthZ/useAuthZ');
|
||||
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
|
||||
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
|
||||
|
||||
const CUSTOM_ROLE_ID = '019c24aa-3333-0001-aaaa-111111111111';
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { render, screen, userEvent, within } from 'tests/test-utils';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import CreateEditRolePage from '../CreateEditRolePage';
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
|
||||
jest.mock('hooks/useAuthZ/useAuthZ');
|
||||
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
|
||||
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { render, screen, userEvent, waitFor, within } from 'tests/test-utils';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
|
||||
import CreateEditRolePage from '../CreateEditRolePage';
|
||||
|
||||
jest.mock('hooks/useAuthZ/useAuthZ');
|
||||
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
|
||||
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
|
||||
|
||||
async function expandAllCards(): Promise<void> {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getResourcePanel } from '../../permissions.config';
|
||||
import ItemInputSelector from './ItemInputSelector';
|
||||
|
||||
import styles from './ActionToggle.module.scss';
|
||||
import { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
|
||||
import { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
|
||||
import { getActionLabel } from 'container/RolesSettings/ViewRolePage/components/permissionDisplay.utils';
|
||||
|
||||
const SCOPE_LABELS: Record<PermissionScope, string> = {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { ConfirmDialog } from '@signozhq/ui/dialog';
|
||||
import { RadioGroup, RadioGroupItem } from '@signozhq/ui/radio-group';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import { Skeleton } from 'antd';
|
||||
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
|
||||
import type { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
|
||||
|
||||
import { PermissionScope, ResourcePermissions } from '../../types';
|
||||
import type { EditorMode, JsonEditorRef } from './JsonEditor.types';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { ChevronDown, ChevronRight } from '@signozhq/icons';
|
||||
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
|
||||
import type { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
|
||||
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Monaco } from '@monaco-editor/react';
|
||||
import permissionsType from 'hooks/useAuthZ/permissions.type';
|
||||
import permissionsType from 'lib/authz/hooks/useAuthZ/permissions.type';
|
||||
import transactionGroupSchema from 'schemas/generated/transactionGroups.schema.json';
|
||||
|
||||
export const TRANSACTION_GROUP_SCHEMA = transactionGroupSchema;
|
||||
|
||||
@@ -5,10 +5,10 @@ import { Pagination, Skeleton } from 'antd';
|
||||
import { useListRoles } from 'api/generated/services/role';
|
||||
import { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||
import PermissionDeniedFullPage from 'components/PermissionDeniedFullPage/PermissionDeniedFullPage';
|
||||
import PermissionDeniedFullPage from 'lib/authz/components/PermissionDeniedFullPage/PermissionDeniedFullPage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { RoleListPermission } from 'hooks/useAuthZ/permissions/role.permissions';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { RoleListPermission } from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { useRolesFeatureGate } from 'hooks/useRolesFeatureGate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import LineClampedText from 'periscope/components/LineClampedText/LineClampedText';
|
||||
|
||||
@@ -3,9 +3,9 @@ import { useHistory } from 'react-router-dom';
|
||||
import { Plus } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { RoleCreatePermission } from 'hooks/useAuthZ/permissions/role.permissions';
|
||||
import { RoleCreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
|
||||
import { useRolesFeatureGate } from 'hooks/useRolesFeatureGate';
|
||||
|
||||
import RolesListingTable from './RolesComponents/RolesListingTable';
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Typography } from '@signozhq/ui/typography';
|
||||
import { Skeleton } from 'antd';
|
||||
import { useGetRole } from 'api/generated/services/role';
|
||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||
import PermissionDeniedFullPage from 'components/PermissionDeniedFullPage/PermissionDeniedFullPage';
|
||||
import PermissionDeniedFullPage from 'lib/authz/components/PermissionDeniedFullPage/PermissionDeniedFullPage';
|
||||
import { useDeleteRoleModal } from 'container/RolesSettings/DeleteRoleModal/useDeleteRoleModal';
|
||||
import { useRoleAuthZ } from 'container/RolesSettings/hooks/useRoleAuthZ';
|
||||
import { transformApiToRolePermissions } from 'container/RolesSettings/hooks/useRolePermissions';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { TooltipProvider } from '@signozhq/ui/tooltip';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as roleApi from 'api/generated/services/role';
|
||||
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
|
||||
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import {
|
||||
customRoleResponse,
|
||||
managedRoleResponse,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
mockUseAuthZDenyAll,
|
||||
mockUseAuthZGrantAll,
|
||||
mockUseAuthZGrantByPrefix,
|
||||
} from 'tests/authz-test-utils';
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
import { render, screen, waitFor } from 'tests/test-utils';
|
||||
|
||||
import * as useRolePermissionsModule from '../../hooks/useRolePermissions';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as roleApi from 'api/generated/services/role';
|
||||
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
|
||||
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { customRoleResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
|
||||
import { render, screen } from 'tests/test-utils';
|
||||
|
||||
import * as useRolePermissionsModule from '../../hooks/useRolePermissions';
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as roleApi from 'api/generated/services/role';
|
||||
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
|
||||
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { customRoleResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
|
||||
import { render, screen } from 'tests/test-utils';
|
||||
|
||||
import * as useRolePermissionsModule from '../../hooks/useRolePermissions';
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import * as roleApi from 'api/generated/services/role';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
|
||||
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { defaultFeatureFlags, render, screen } from 'tests/test-utils';
|
||||
import { invalidLicense, mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import {
|
||||
invalidLicense,
|
||||
mockUseAuthZGrantAll,
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import ViewRolePage from '../ViewRolePage';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as roleApi from 'api/generated/services/role';
|
||||
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
|
||||
import { render } from 'tests/test-utils';
|
||||
|
||||
import ViewRolePage from '../ViewRolePage';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as roleApi from 'api/generated/services/role';
|
||||
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
|
||||
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { customRoleResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { render, screen, within } from 'tests/test-utils';
|
||||
|
||||
|
||||
@@ -3,12 +3,12 @@ import {
|
||||
CoretypesTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import * as roleApi from 'api/generated/services/role';
|
||||
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
|
||||
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import {
|
||||
customRoleResponse,
|
||||
managedRoleResponse,
|
||||
} from 'mocks-server/__mockdata__/roles';
|
||||
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import * as useRolePermissionsModule from '../../hooks/useRolePermissions';
|
||||
|
||||
|
||||
@@ -11,12 +11,15 @@ import {
|
||||
userEvent,
|
||||
} from 'tests/test-utils';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { invalidLicense, mockUseAuthZGrantAll } from 'tests/authz-test-utils';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import {
|
||||
invalidLicense,
|
||||
mockUseAuthZGrantAll,
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import RolesSettings from '../RolesSettings';
|
||||
|
||||
jest.mock('hooks/useAuthZ/useAuthZ');
|
||||
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
|
||||
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
|
||||
|
||||
const rolesApiURL = 'http://localhost/api/v1/roles';
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
CoretypesKindDTO,
|
||||
CoretypesTypeDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
|
||||
import type { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
|
||||
|
||||
import {
|
||||
ActionConfig,
|
||||
|
||||
@@ -4,9 +4,12 @@ import {
|
||||
buildRoleReadPermission,
|
||||
buildRoleUpdatePermission,
|
||||
RoleCreatePermission,
|
||||
} from 'hooks/useAuthZ/permissions/role.permissions';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { ParsedPermissionObject, parsePermission } from 'hooks/useAuthZ/utils';
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import {
|
||||
ParsedPermissionObject,
|
||||
parsePermission,
|
||||
} from 'lib/authz/hooks/useAuthZ/utils';
|
||||
|
||||
interface UseRoleAuthZResult {
|
||||
readRolePermission: ParsedPermissionObject;
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
useGetRole,
|
||||
useUpdateRole,
|
||||
} from 'api/generated/services/role';
|
||||
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
|
||||
import type { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
|
||||
|
||||
import {
|
||||
getResourcePanel,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { Bot, Key, Shield } from '@signozhq/icons';
|
||||
|
||||
import permissionsType from 'hooks/useAuthZ/permissions.type';
|
||||
import permissionsType from 'lib/authz/hooks/useAuthZ/permissions.type';
|
||||
import {
|
||||
AuthZResource,
|
||||
AuthZVerb,
|
||||
OBJECT_SCOPED_VERBS,
|
||||
ObjectScopedVerb,
|
||||
} from 'hooks/useAuthZ/types';
|
||||
} from 'lib/authz/hooks/useAuthZ/types';
|
||||
import { CoretypesTypeDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
/** Shared shape of the icon components exported by `@signozhq/icons`. */
|
||||
@@ -84,7 +84,7 @@ export function getResourceVerbs(
|
||||
}
|
||||
|
||||
// Role resource cannot have assignee verb
|
||||
// TODO(H4ad): Remove this once we get rid of frontend/src/hooks/useAuthZ/legacy.ts
|
||||
// TODO(H4ad): Remove this once we get rid of frontend/lib/authz/hooks/useAuthZ/legacy.ts
|
||||
if (resource === 'role') {
|
||||
return match.allowedVerbs.filter((verb) => verb !== 'assignee');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
|
||||
import type { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
|
||||
import { CoretypesTypeDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
export enum PermissionScope {
|
||||
|
||||
@@ -3,7 +3,10 @@ import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { render, screen, waitFor } from 'tests/test-utils';
|
||||
import { AUTHZ_CHECK_URL, authzMockResponse } from 'tests/authz-test-utils';
|
||||
import {
|
||||
AUTHZ_CHECK_URL,
|
||||
authzMockResponse,
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
import ServiceAccountsSettings from './ServiceAccountsSettings';
|
||||
|
||||
const SA_LIST_URL = 'http://localhost/api/v1/service_accounts';
|
||||
|
||||
@@ -4,10 +4,10 @@ import { Button } from '@signozhq/ui/button';
|
||||
import { DropdownMenuSimple, type MenuItem } from '@signozhq/ui/dropdown-menu';
|
||||
import { Input } from '@signozhq/ui/input';
|
||||
import { useListServiceAccounts } from 'api/generated/services/serviceaccount';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
|
||||
import CreateServiceAccountModal from 'components/CreateServiceAccountModal/CreateServiceAccountModal';
|
||||
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
|
||||
import PermissionDeniedFullPage from 'components/PermissionDeniedFullPage/PermissionDeniedFullPage';
|
||||
import PermissionDeniedFullPage from 'lib/authz/components/PermissionDeniedFullPage/PermissionDeniedFullPage';
|
||||
import Spinner from 'components/Spinner';
|
||||
import ServiceAccountDrawer from 'components/ServiceAccountDrawer/ServiceAccountDrawer';
|
||||
import ServiceAccountsTable, {
|
||||
@@ -16,8 +16,8 @@ import ServiceAccountsTable, {
|
||||
import {
|
||||
SACreatePermission,
|
||||
SAListPermission,
|
||||
} from 'hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import {
|
||||
parseAsBoolean,
|
||||
parseAsInteger,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { listRolesSuccessResponse } from 'mocks-server/__mockdata__/roles';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||
import { setupAuthzAdmin } from 'tests/authz-test-utils';
|
||||
import { setupAuthzAdmin } from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import ServiceAccountsSettings from '../ServiceAccountsSettings';
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import GetMinMax from 'lib/getMinMax';
|
||||
import getTimeString from 'lib/getTimeString';
|
||||
import history from 'lib/history';
|
||||
import { stackSeries } from 'container/DashboardContainer/visualization/charts/utils/stackSeriesUtils';
|
||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
||||
import { isEmpty } from 'lodash-es';
|
||||
@@ -57,6 +58,7 @@ function TimeSeriesView({
|
||||
dataSource,
|
||||
setWarning,
|
||||
panelType = PANEL_TYPES.TIME_SERIES,
|
||||
stackBarChart = false,
|
||||
}: TimeSeriesViewProps): JSX.Element {
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -65,11 +67,23 @@ function TimeSeriesView({
|
||||
const location = useLocation();
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
|
||||
const chartData = useMemo(
|
||||
const rawChartData = useMemo(
|
||||
() => getUPlotChartData(data?.payload),
|
||||
[data?.payload],
|
||||
);
|
||||
|
||||
const { chartData, stackedBands } = useMemo(() => {
|
||||
if (!stackBarChart || !rawChartData || rawChartData.length < 2) {
|
||||
return { chartData: rawChartData, stackedBands: null };
|
||||
}
|
||||
const noSeriesHidden = (): boolean => false;
|
||||
const { data: stacked, bands } = stackSeries(
|
||||
rawChartData as uPlot.AlignedData,
|
||||
noSeriesHidden,
|
||||
);
|
||||
return { chartData: stacked, stackedBands: bands };
|
||||
}, [rawChartData, stackBarChart]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data?.payload) {
|
||||
setWarning?.(data?.warning);
|
||||
@@ -189,7 +203,7 @@ function TimeSeriesView({
|
||||
|
||||
const { timezone } = useTimezone();
|
||||
|
||||
const chartOptions = getUPlotChartOptions({
|
||||
const baseChartOptions = getUPlotChartOptions({
|
||||
id: 'time-series-explorer',
|
||||
onDragSelect,
|
||||
yAxisUnit: yAxisUnit || '',
|
||||
@@ -222,6 +236,14 @@ function TimeSeriesView({
|
||||
},
|
||||
});
|
||||
|
||||
const chartOptions = useMemo(
|
||||
() =>
|
||||
stackedBands
|
||||
? { ...baseChartOptions, bands: stackedBands }
|
||||
: baseChartOptions,
|
||||
[baseChartOptions, stackedBands],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="time-series-view">
|
||||
{isError && error && <ErrorInPlace error={error as APIError} />}
|
||||
@@ -282,6 +304,7 @@ interface TimeSeriesViewProps {
|
||||
dataSource: DataSource;
|
||||
setWarning?: Dispatch<SetStateAction<Warning | undefined>>;
|
||||
panelType?: PANEL_TYPES;
|
||||
stackBarChart?: boolean;
|
||||
}
|
||||
|
||||
TimeSeriesView.defaultProps = {
|
||||
@@ -290,6 +313,7 @@ TimeSeriesView.defaultProps = {
|
||||
error: undefined,
|
||||
setWarning: undefined,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
stackBarChart: false,
|
||||
};
|
||||
|
||||
export default TimeSeriesView;
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { ReactElement } from 'react';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
import { buildPermission } from 'hooks/useAuthZ/utils';
|
||||
import type { AuthZObject, BrandedPermission } from 'hooks/useAuthZ/types';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { buildPermission } from 'lib/authz/hooks/useAuthZ/utils';
|
||||
import type {
|
||||
AuthZObject,
|
||||
BrandedPermission,
|
||||
} from 'lib/authz/hooks/useAuthZ/types';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import AuthZTooltip from './AuthZTooltip';
|
||||
|
||||
jest.mock('hooks/useAuthZ/useAuthZ');
|
||||
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
|
||||
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
|
||||
|
||||
const noPermissions = {
|
||||
@@ -5,9 +5,9 @@ import {
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from '@signozhq/ui/tooltip';
|
||||
import type { BrandedPermission } from 'hooks/useAuthZ/types';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { formatPermission } from 'hooks/useAuthZ/utils';
|
||||
import type { BrandedPermission } from 'lib/authz/hooks/useAuthZ/types';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { formatPermission } from 'lib/authz/hooks/useAuthZ/utils';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import styles from './AuthZTooltip.module.scss';
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { ReactElement } from 'react';
|
||||
import { BrandedPermission } from 'hooks/useAuthZ/types';
|
||||
import { buildPermission } from 'hooks/useAuthZ/utils';
|
||||
import { BrandedPermission } from 'lib/authz/hooks/useAuthZ/types';
|
||||
import { buildPermission } from 'lib/authz/hooks/useAuthZ/utils';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { render, screen, waitFor } from 'tests/test-utils';
|
||||
import { AUTHZ_CHECK_URL, authzMockResponse } from 'tests/authz-test-utils';
|
||||
import {
|
||||
AUTHZ_CHECK_URL,
|
||||
authzMockResponse,
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import { GuardAuthZ } from './GuardAuthZ';
|
||||
|
||||
@@ -3,9 +3,9 @@ import {
|
||||
AuthZObject,
|
||||
AuthZRelation,
|
||||
BrandedPermission,
|
||||
} from 'hooks/useAuthZ/types';
|
||||
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
|
||||
import { buildPermission } from 'hooks/useAuthZ/utils';
|
||||
} from 'lib/authz/hooks/useAuthZ/types';
|
||||
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
|
||||
import { buildPermission } from 'lib/authz/hooks/useAuthZ/utils';
|
||||
|
||||
export type GuardAuthZProps<R extends AuthZRelation> = {
|
||||
children: ReactElement;
|
||||
@@ -7,7 +7,10 @@ import type {
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { render, screen, waitFor } from 'tests/test-utils';
|
||||
import { AUTHZ_CHECK_URL, authzMockResponse } from 'tests/authz-test-utils';
|
||||
import {
|
||||
AUTHZ_CHECK_URL,
|
||||
authzMockResponse,
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import { createGuardedRoute } from './createGuardedRoute';
|
||||
|
||||
@@ -4,13 +4,13 @@ import {
|
||||
AuthZObject,
|
||||
AuthZRelation,
|
||||
BrandedPermission,
|
||||
} from 'hooks/useAuthZ/types';
|
||||
import { formatPermission } from 'hooks/useAuthZ/utils';
|
||||
} from 'lib/authz/hooks/useAuthZ/types';
|
||||
import { formatPermission } from 'lib/authz/hooks/useAuthZ/utils';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
|
||||
import noDataUrl from '@/assets/Icons/no-data.svg';
|
||||
import noDataUrl from 'assets/Icons/no-data.svg';
|
||||
|
||||
import AppLoading from '../AppLoading/AppLoading';
|
||||
import AppLoading from '../../../../components/AppLoading/AppLoading';
|
||||
import { GuardAuthZ } from '../GuardAuthZ/GuardAuthZ';
|
||||
|
||||
import './createGuardedRoute.styles.scss';
|
||||
@@ -3,7 +3,10 @@ import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { AllTheProviders } from 'tests/test-utils';
|
||||
import { AUTHZ_CHECK_URL, authzMockResponse } from 'tests/authz-test-utils';
|
||||
import {
|
||||
AUTHZ_CHECK_URL,
|
||||
authzMockResponse,
|
||||
} from 'lib/authz/utils/authz-test-utils';
|
||||
|
||||
import { BrandedPermission } from './types';
|
||||
import { useAuthZ } from './useAuthZ';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user