Compare commits

..

3 Commits

Author SHA1 Message Date
Abhi Kumar
30e5b68e43 fix(dashboards-v2): refine New Panel modal footer + section labels
- Keep the footer visible at all times; the "Add Panel" confirm is disabled
  until a panel type is selected (instead of hiding the whole footer).
- Footer layout: the section selector fills the available width and the
  confirm button takes its natural width (no more 50/50 split).
- Add a "Select panel type" label above the panel-type grid, matching the
  existing "Add panel to" label.
2026-07-02 20:11:41 +05:30
Abhi Kumar
093eda7deb feat(dashboards-v2): choose target section when adding a panel
Add a section picker to the New Panel modal so a panel can be placed in
any section (or the untitled root), not just the one the "Add panel"
action was triggered from. Sections are read from the cached dashboard
query via useDashboardSections (no refetch, no prop-drilling).

Picking a panel type now selects it (highlighted card) and reveals a
footer holding the section dropdown and an "Add Panel" button split
50/50; confirming shows a brief loader before navigating to the editor.
The chosen section's layoutIndex is threaded through
useCreatePanel.createPanel.
2026-07-02 17:23:05 +05:30
Abhi Kumar
77f7a2cceb fix: ui fixes for panel selection modal 2026-07-02 14:30:25 +05:30
207 changed files with 1991 additions and 5170 deletions

5
.github/CODEOWNERS vendored
View File

@@ -109,7 +109,10 @@ go.mod @therealpandey
/pkg/modules/role/ @therealpandey
/pkg/types/coretypes/ @therealpandey @vikrantgupta25
/frontend/src/lib/authz/ @H4ad
/frontend/src/hooks/useAuthZ/ @H4ad
/frontend/src/components/GuardAuthZ/ @H4ad
/frontend/src/components/AuthZTooltip/ @H4ad
/frontend/src/components/createGuardedRoute/ @H4ad
/frontend/src/container/RolesSettings/ @H4ad
/frontend/src/components/RolesSelect/ @H4ad
/frontend/src/pages/MembersSettings/ @H4ad

View File

@@ -61,6 +61,13 @@ 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

View File

@@ -67,6 +67,12 @@ 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

View File

@@ -27,6 +27,13 @@ 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

View File

@@ -12,7 +12,7 @@ import (
"github.com/spf13/cobra"
)
const permissionsTypePath = "frontend/src/lib/authz/hooks/useAuthZ/permissions.type.ts"
const permissionsTypePath = "frontend/src/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

View File

@@ -515,13 +515,6 @@ components:
url:
type: string
type: object
AuthtypesDeprecatedPostableUserRole:
properties:
id:
type: string
required:
- id
type: object
AuthtypesGettableAuthDomain:
properties:
authNProviderInfo:
@@ -667,20 +660,17 @@ components:
type: string
userRoles:
items:
$ref: '#/components/schemas/AuthtypesDeprecatedPostableUserRole'
$ref: '#/components/schemas/AuthtypesPostableUserRole'
type: array
required:
- email
type: object
AuthtypesPostableUserRole:
properties:
roleId:
type: string
userId:
id:
type: string
required:
- userId
- roleId
- id
type: object
AuthtypesRelation:
enum:
@@ -3081,10 +3071,6 @@ components:
items:
$ref: '#/components/schemas/DashboardtypesListedDashboardForUserV2'
type: array
reservedKeywords:
items:
type: string
type: array
tags:
items:
$ref: '#/components/schemas/TagtypesGettableTag'
@@ -3096,7 +3082,6 @@ components:
- dashboards
- total
- tags
- reservedKeywords
type: object
DashboardtypesListableDashboardV2:
properties:
@@ -3104,10 +3089,6 @@ components:
items:
$ref: '#/components/schemas/DashboardtypesListedDashboardV2'
type: array
reservedKeywords:
items:
type: string
type: array
tags:
items:
$ref: '#/components/schemas/TagtypesGettableTag'
@@ -3119,7 +3100,6 @@ components:
- dashboards
- total
- tags
- reservedKeywords
type: object
DashboardtypesListableDashboardView:
properties:
@@ -7410,13 +7390,6 @@ components:
enum:
- basic
type: string
ServiceaccounttypesDeprecatedPostableServiceAccountRole:
properties:
id:
type: string
required:
- id
type: object
ServiceaccounttypesGettableFactorAPIKey:
properties:
createdAt:
@@ -7473,13 +7446,10 @@ components:
type: object
ServiceaccounttypesPostableServiceAccountRole:
properties:
roleId:
type: string
serviceAccountId:
id:
type: string
required:
- serviceAccountId
- roleId
- id
type: object
ServiceaccounttypesServiceAccount:
properties:
@@ -12188,188 +12158,6 @@ paths:
summary: Update route policy
tags:
- routepolicies
/api/v1/service_account_roles:
post:
deprecated: false
description: This endpoint assigns a role to a service account
operationId: CreateServiceAccountRole
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceaccounttypesPostableServiceAccountRole'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/TypesIdentifiable'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- serviceaccount:attach
- role:attach
- tokenizer:
- serviceaccount:attach
- role:attach
summary: Create service account role
tags:
- serviceaccount
/api/v1/service_account_roles/{id}:
delete:
deprecated: false
description: This endpoint revokes a role from a service account
operationId: DeleteServiceAccountRole
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- serviceaccount:detach
- role:detach
- tokenizer:
- serviceaccount:detach
- role:detach
summary: Delete service account role
tags:
- serviceaccount
get:
deprecated: false
description: This endpoint gets an existing service account role
operationId: GetServiceAccountRole
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/ServiceaccounttypesServiceAccountRole'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- serviceaccount:read
- tokenizer:
- serviceaccount:read
summary: Get service account role
tags:
- serviceaccount
/api/v1/service_accounts:
get:
deprecated: false
@@ -12939,9 +12727,9 @@ paths:
tags:
- serviceaccount
post:
deprecated: true
deprecated: false
description: This endpoint assigns a role to a service account
operationId: CreateServiceAccountRoleDeprecated
operationId: CreateServiceAccountRole
parameters:
- in: path
name: id
@@ -12952,7 +12740,7 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceaccounttypesDeprecatedPostableServiceAccountRole'
$ref: '#/components/schemas/ServiceaccounttypesPostableServiceAccountRole'
responses:
"201":
content:
@@ -13004,9 +12792,9 @@ paths:
- serviceaccount
/api/v1/service_accounts/{id}/roles/{rid}:
delete:
deprecated: true
deprecated: false
description: This endpoint revokes a role from service account
operationId: DeleteServiceAccountRoleDeprecated
operationId: DeleteServiceAccountRole
parameters:
- in: path
name: id
@@ -14171,184 +13959,6 @@ paths:
summary: Update user preference
tags:
- preferences
/api/v1/user_roles:
post:
deprecated: false
description: This endpoint assigns a role to a user
operationId: CreateUserRole
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/AuthtypesPostableUserRole'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/TypesIdentifiable'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create user role
tags:
- users
/api/v1/user_roles/{id}:
delete:
deprecated: false
description: This endpoint revokes a role from a user
operationId: DeleteUserRole
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Delete user role
tags:
- users
get:
deprecated: false
description: This endpoint gets an existing user role
operationId: GetUserRole
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/AuthtypesUserRole'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Get user role
tags:
- users
/api/v2/dashboard_views:
get:
deprecated: false
@@ -22607,7 +22217,7 @@ paths:
tags:
- users
post:
deprecated: true
deprecated: false
description: This endpoint assigns the role to the user roles by user id
operationId: SetRoleByUserID
parameters:
@@ -22658,7 +22268,7 @@ paths:
- users
/api/v2/users/{id}/roles/{roleId}:
delete:
deprecated: true
deprecated: false
description: This endpoint removes a role from the user by user id and role
id
operationId: RemoveUserRoleByUserIDAndRoleID

View File

@@ -519,7 +519,7 @@ func (module *module) getOrCreateAPIKey(ctx context.Context, orgID valuer.UUID,
if err != nil {
return "", err
}
_, err = module.serviceAccount.SetRoleByName(ctx, orgID, serviceAccount.ID, authtypes.SigNozViewerRoleName)
err = module.serviceAccount.SetRoleByName(ctx, orgID, serviceAccount.ID, authtypes.SigNozViewerRoleName)
if err != nil {
return "", err
}

View File

@@ -141,7 +141,7 @@ func (ah *APIHandler) getOrCreateCloudIntegrationServiceAccount(ctx context.Cont
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration service account: %w", err))
}
_, err = ah.Signoz.Modules.ServiceAccount.SetRoleByName(ctx, orgId, cloudIntegrationServiceAccount.ID, authtypes.SigNozViewerRoleName)
err = ah.Signoz.Modules.ServiceAccount.SetRoleByName(ctx, orgId, cloudIntegrationServiceAccount.ID, authtypes.SigNozViewerRoleName)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("couldn't create cloud integration service account: %w", err))
}

View File

@@ -1,116 +1,105 @@
<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>
# Configuring Over Local
1. Docker
1. Without 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>
## With Docker
# SigNoz Frontend
**Building image**
React-based web interface for [SigNoz](https://signoz.io), the open-source observability platform.
``docker compose up`
/ This will also run
## Tech Stack
or
`docker build . -t tagname`
- **Framework:** React 18 + TypeScript
- **Build:** Vite
- **State:** React Query, Zustand, Redux Toolkit (legacy)
- **Styling:** CSS Modules, Ant Design (legacy)
- **Charts:** uPlot
- **Testing:** Jest
## 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
```
Opens [http://localhost:3301](http://localhost:3301).
## Build
```bash
pnpm build
```
Output in `build/` folder.
## Bundle Size Analysis
Set in `.env`:
```bash
BUNDLE_ANALYSER="true"
```
Then run build:
```bash
pnpm build
```
Opens bundle analyzer visualization automatically.
## Testing
```bash
# Unit tests
pnpm test
# Type checking
pnpm tsgo --noEmit
```
## Linting
```bash
# Run all linters (oxlint + stylelint)
pnpm lint
```
## Project Structure
**Tag to remote url- Introduce versioning later on**
```
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
docker tag signoz/frontend:latest 7296823551/signoz:latest
```
## Contributing
```
docker compose up
```
See [CONTRIBUTING.md](../CONTRIBUTING.md) in the root repo.
## Without Docker
Follow the steps below
Questions? Join our [Slack community](https://signoz.io/slack).
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```
1. ```pnpm install```
1. ```pnpm dev```
```Note: Please ping us in #contributing channel in our slack community and we will DM you with <test environment URL>```
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `pnpm start`
Runs the app in the development mode.\
Open [http://localhost:3301](http://localhost:3301) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `pnpm test`
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.
### `pnpm build`
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 cant go back!**
If you arent 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 youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt 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)

View File

@@ -1,38 +1,8 @@
NODE_ENV="development"
BUNDLE_ANALYSER="true"
CI="1"
# API
VITE_BASE_PATH=""
VITE_FRONTEND_API_ENDPOINT="http://localhost:8080"
VITE_WEBSOCKET_API_ENDPOINT=""
VITE_PYLON_APP_ID="pylon-app-id"
VITE_APPCUES_APP_ID="appcess-app-id"
VITE_PYLON_IDENTITY_SECRET="pylon-identity-secret"
# 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=""
CI="1"

View File

@@ -111,10 +111,11 @@
<div id="root"></div>
<script>
var PYLON_APP_ID = '<%- PYLON_APP_ID %>';
var pylonSettings =
((window.signozBootData || {}).settings || {}).pylon || {};
var pylonEnabled = pylonSettings.enabled === true;
if (pylonSettings.appId && pylonEnabled) {
var pylonEnabled = pylonSettings.enabled !== false;
if (PYLON_APP_ID && pylonEnabled) {
(function () {
var e = window;
var t = document;
@@ -132,7 +133,7 @@
e.setAttribute('async', 'true');
e.setAttribute(
'src',
'https://widget.usepylon.com/widget/' + pylonSettings.appId,
'https://widget.usepylon.com/widget/' + PYLON_APP_ID,
);
var n = t.getElementsByTagName('script')[0];
n.parentNode.insertBefore(e, n);
@@ -149,14 +150,15 @@
window.AppcuesSettings = { enableURLDetection: true };
</script>
<script>
var APPCUES_APP_ID = '<%- APPCUES_APP_ID %>';
var appcuesSettings =
((window.signozBootData || {}).settings || {}).appcues || {};
var appcuesEnabled = appcuesSettings.enabled === true;
if (appcuesSettings.appId && appcuesEnabled) {
var appcuesEnabled = appcuesSettings.enabled !== false;
if (APPCUES_APP_ID && appcuesEnabled) {
(function (d, t) {
var a = d.createElement(t);
a.async = 1;
a.src = '//fast.appcues.com/' + appcuesSettings.appId + '.js';
a.src = '//fast.appcues.com/' + APPCUES_APP_ID + '.js';
var s = d.getElementsByTagName(t)[0];
s.parentNode.insertBefore(a, s);
})(document, 'script');

View File

@@ -3,6 +3,7 @@
"project": ["src/**/*.ts", "src/**/*.tsx"],
"ignore": ["src/api/generated/**/*.ts", "src/typings/*.ts"],
"ignoreDependencies": [
"http-proxy-middleware",
"@typescript/native-preview"
]
}

View File

@@ -79,6 +79,7 @@
"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",

View File

@@ -164,6 +164,9 @@ 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
@@ -5458,6 +5461,10 @@ 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==}
@@ -5465,6 +5472,9 @@ 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'}
@@ -14505,6 +14515,16 @@ 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:
@@ -14514,6 +14534,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
httpxy@0.5.3: {}
human-signals@2.1.0: {}
human-signals@8.0.1: {}

View File

@@ -292,10 +292,10 @@ function App(): JSX.Element {
isChatSupportEnabled &&
!showAddCreditCardModal &&
(isCloudUser || isEnterpriseSelfHostedUser) &&
window.signozBootData?.settings?.pylon?.enabled
(window.signozBootData?.settings?.pylon.enabled ?? true)
) {
const email = user.email || '';
const secret = window.signozBootData?.settings?.pylon?.identitySecret || '';
const secret = process.env.PYLON_IDENTITY_SECRET || '';
let emailHash = '';
if (email && secret) {
@@ -304,7 +304,7 @@ function App(): JSX.Element {
window.pylon = {
chat_settings: {
app_id: window.signozBootData?.settings?.pylon?.appId,
app_id: process.env.PYLON_APP_ID,
email: user.email,
name: user.displayName || user.email,
email_hash: emailHash,
@@ -335,23 +335,22 @@ function App(): JSX.Element {
useEffect(() => {
if (isCloudUser || isEnterpriseSelfHostedUser) {
if (
window.signozBootData?.settings?.posthog?.enabled &&
window.signozBootData?.settings?.posthog?.key
(window.signozBootData?.settings?.posthog.enabled ?? true) &&
process.env.POSTHOG_KEY
) {
posthog.init(window.signozBootData.settings.posthog.key, {
api_host: window.signozBootData.settings.posthog.apiHost,
ui_host: window.signozBootData.settings.posthog.uiHost,
posthog.init(process.env.POSTHOG_KEY, {
api_host: 'https://us.i.posthog.com',
person_profiles: 'identified_only', // or 'always' to create profiles for anonymous users as well
});
}
if (
!isSentryInitialized &&
window.signozBootData?.settings?.sentry?.enabled
(window.signozBootData?.settings?.sentry.enabled ?? true)
) {
Sentry.init({
dsn: window.signozBootData.settings.sentry.dsn,
tunnel: window.signozBootData.settings.sentry.tunnel,
dsn: process.env.SENTRY_DSN,
tunnel: process.env.TUNNEL_URL,
environment: process.env.ENVIRONMENT,
release: process.env.VERSION,
integrations: [

View File

@@ -22,16 +22,12 @@ import type {
CreateServiceAccountKey201,
CreateServiceAccountKeyPathParameters,
CreateServiceAccountRole201,
CreateServiceAccountRoleDeprecated201,
CreateServiceAccountRoleDeprecatedPathParameters,
CreateServiceAccountRolePathParameters,
DeleteServiceAccountPathParameters,
DeleteServiceAccountRoleDeprecatedPathParameters,
DeleteServiceAccountRolePathParameters,
GetMyServiceAccount200,
GetServiceAccount200,
GetServiceAccountPathParameters,
GetServiceAccountRole200,
GetServiceAccountRolePathParameters,
GetServiceAccountRoles200,
GetServiceAccountRolesPathParameters,
ListServiceAccountKeys200,
@@ -39,7 +35,6 @@ import type {
ListServiceAccounts200,
RenderErrorResponseDTO,
RevokeServiceAccountKeyPathParameters,
ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO,
ServiceaccounttypesPostableFactorAPIKeyDTO,
ServiceaccounttypesPostableServiceAccountDTO,
ServiceaccounttypesPostableServiceAccountRoleDTO,
@@ -51,272 +46,6 @@ import type {
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { ErrorType, BodyType } from '../../../generatedAPIInstance';
/**
* This endpoint assigns a role to a service account
* @summary Create service account role
*/
export const createServiceAccountRole = (
serviceaccounttypesPostableServiceAccountRoleDTO?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateServiceAccountRole201>({
url: `/api/v1/service_account_roles`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: serviceaccounttypesPostableServiceAccountRoleDTO,
signal,
});
};
export const getCreateServiceAccountRoleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccountRole>>,
TError,
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccountRole>>,
TError,
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> },
TContext
> => {
const mutationKey = ['createServiceAccountRole'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createServiceAccountRole>>,
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> }
> = (props) => {
const { data } = props ?? {};
return createServiceAccountRole(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateServiceAccountRoleMutationResult = NonNullable<
Awaited<ReturnType<typeof createServiceAccountRole>>
>;
export type CreateServiceAccountRoleMutationBody =
| BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>
| undefined;
export type CreateServiceAccountRoleMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create service account role
*/
export const useCreateServiceAccountRole = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccountRole>>,
TError,
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createServiceAccountRole>>,
TError,
{ data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO> },
TContext
> => {
return useMutation(getCreateServiceAccountRoleMutationOptions(options));
};
/**
* This endpoint revokes a role from a service account
* @summary Delete service account role
*/
export const deleteServiceAccountRole = (
{ id }: DeleteServiceAccountRolePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/service_account_roles/${id}`,
method: 'DELETE',
signal,
});
};
export const getDeleteServiceAccountRoleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
TError,
{ pathParams: DeleteServiceAccountRolePathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
TError,
{ pathParams: DeleteServiceAccountRolePathParameters },
TContext
> => {
const mutationKey = ['deleteServiceAccountRole'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
{ pathParams: DeleteServiceAccountRolePathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteServiceAccountRole(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteServiceAccountRoleMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteServiceAccountRole>>
>;
export type DeleteServiceAccountRoleMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete service account role
*/
export const useDeleteServiceAccountRole = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
TError,
{ pathParams: DeleteServiceAccountRolePathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
TError,
{ pathParams: DeleteServiceAccountRolePathParameters },
TContext
> => {
return useMutation(getDeleteServiceAccountRoleMutationOptions(options));
};
/**
* This endpoint gets an existing service account role
* @summary Get service account role
*/
export const getServiceAccountRole = (
{ id }: GetServiceAccountRolePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetServiceAccountRole200>({
url: `/api/v1/service_account_roles/${id}`,
method: 'GET',
signal,
});
};
export const getGetServiceAccountRoleQueryKey = ({
id,
}: GetServiceAccountRolePathParameters) => {
return [`/api/v1/service_account_roles/${id}`] as const;
};
export const getGetServiceAccountRoleQueryOptions = <
TData = Awaited<ReturnType<typeof getServiceAccountRole>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetServiceAccountRolePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getServiceAccountRole>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetServiceAccountRoleQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getServiceAccountRole>>
> = ({ signal }) => getServiceAccountRole({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getServiceAccountRole>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetServiceAccountRoleQueryResult = NonNullable<
Awaited<ReturnType<typeof getServiceAccountRole>>
>;
export type GetServiceAccountRoleQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get service account role
*/
export function useGetServiceAccountRole<
TData = Awaited<ReturnType<typeof getServiceAccountRole>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetServiceAccountRolePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getServiceAccountRole>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetServiceAccountRoleQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
return { ...query, queryKey: queryOptions.queryKey };
}
/**
* @summary Get service account role
*/
export const invalidateGetServiceAccountRole = async (
queryClient: QueryClient,
{ id }: GetServiceAccountRolePathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetServiceAccountRoleQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint lists the service accounts for an organisation
* @summary List service accounts
@@ -1255,46 +984,45 @@ export const invalidateGetServiceAccountRoles = async (
/**
* This endpoint assigns a role to a service account
* @deprecated
* @summary Create service account role
*/
export const createServiceAccountRoleDeprecated = (
{ id }: CreateServiceAccountRoleDeprecatedPathParameters,
serviceaccounttypesDeprecatedPostableServiceAccountRoleDTO?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>,
export const createServiceAccountRole = (
{ id }: CreateServiceAccountRolePathParameters,
serviceaccounttypesPostableServiceAccountRoleDTO?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateServiceAccountRoleDeprecated201>({
return GeneratedAPIInstance<CreateServiceAccountRole201>({
url: `/api/v1/service_accounts/${id}/roles`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: serviceaccounttypesDeprecatedPostableServiceAccountRoleDTO,
data: serviceaccounttypesPostableServiceAccountRoleDTO,
signal,
});
};
export const getCreateServiceAccountRoleDeprecatedMutationOptions = <
export const getCreateServiceAccountRoleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
Awaited<ReturnType<typeof createServiceAccountRole>>,
TError,
{
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
pathParams: CreateServiceAccountRolePathParameters;
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
Awaited<ReturnType<typeof createServiceAccountRole>>,
TError,
{
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
pathParams: CreateServiceAccountRolePathParameters;
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
},
TContext
> => {
const mutationKey = ['createServiceAccountRoleDeprecated'];
const mutationKey = ['createServiceAccountRole'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
@@ -1304,66 +1032,62 @@ export const getCreateServiceAccountRoleDeprecatedMutationOptions = <
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
Awaited<ReturnType<typeof createServiceAccountRole>>,
{
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
pathParams: CreateServiceAccountRolePathParameters;
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return createServiceAccountRoleDeprecated(pathParams, data);
return createServiceAccountRole(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateServiceAccountRoleDeprecatedMutationResult = NonNullable<
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>
export type CreateServiceAccountRoleMutationResult = NonNullable<
Awaited<ReturnType<typeof createServiceAccountRole>>
>;
export type CreateServiceAccountRoleDeprecatedMutationBody =
| BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>
export type CreateServiceAccountRoleMutationBody =
| BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>
| undefined;
export type CreateServiceAccountRoleDeprecatedMutationError =
export type CreateServiceAccountRoleMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @deprecated
* @summary Create service account role
*/
export const useCreateServiceAccountRoleDeprecated = <
export const useCreateServiceAccountRole = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
Awaited<ReturnType<typeof createServiceAccountRole>>,
TError,
{
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
pathParams: CreateServiceAccountRolePathParameters;
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createServiceAccountRoleDeprecated>>,
Awaited<ReturnType<typeof createServiceAccountRole>>,
TError,
{
pathParams: CreateServiceAccountRoleDeprecatedPathParameters;
data?: BodyType<ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO>;
pathParams: CreateServiceAccountRolePathParameters;
data?: BodyType<ServiceaccounttypesPostableServiceAccountRoleDTO>;
},
TContext
> => {
return useMutation(
getCreateServiceAccountRoleDeprecatedMutationOptions(options),
);
return useMutation(getCreateServiceAccountRoleMutationOptions(options));
};
/**
* This endpoint revokes a role from service account
* @deprecated
* @summary Delete service account role
*/
export const deleteServiceAccountRoleDeprecated = (
{ id, rid }: DeleteServiceAccountRoleDeprecatedPathParameters,
export const deleteServiceAccountRole = (
{ id, rid }: DeleteServiceAccountRolePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
@@ -1373,23 +1097,23 @@ export const deleteServiceAccountRoleDeprecated = (
});
};
export const getDeleteServiceAccountRoleDeprecatedMutationOptions = <
export const getDeleteServiceAccountRoleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
TError,
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters },
{ pathParams: DeleteServiceAccountRolePathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
TError,
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters },
{ pathParams: DeleteServiceAccountRolePathParameters },
TContext
> => {
const mutationKey = ['deleteServiceAccountRoleDeprecated'];
const mutationKey = ['deleteServiceAccountRole'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
@@ -1399,47 +1123,44 @@ export const getDeleteServiceAccountRoleDeprecatedMutationOptions = <
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters }
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
{ pathParams: DeleteServiceAccountRolePathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteServiceAccountRoleDeprecated(pathParams);
return deleteServiceAccountRole(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteServiceAccountRoleDeprecatedMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>
export type DeleteServiceAccountRoleMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteServiceAccountRole>>
>;
export type DeleteServiceAccountRoleDeprecatedMutationError =
export type DeleteServiceAccountRoleMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @deprecated
* @summary Delete service account role
*/
export const useDeleteServiceAccountRoleDeprecated = <
export const useDeleteServiceAccountRole = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
TError,
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters },
{ pathParams: DeleteServiceAccountRolePathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteServiceAccountRoleDeprecated>>,
Awaited<ReturnType<typeof deleteServiceAccountRole>>,
TError,
{ pathParams: DeleteServiceAccountRoleDeprecatedPathParameters },
{ pathParams: DeleteServiceAccountRolePathParameters },
TContext
> => {
return useMutation(
getDeleteServiceAccountRoleDeprecatedMutationOptions(options),
);
return useMutation(getDeleteServiceAccountRoleMutationOptions(options));
};
/**
* This endpoint gets my service account

View File

@@ -2048,13 +2048,6 @@ export interface AuthtypesAuthNSupportDTO {
password?: AuthtypesPasswordAuthNSupportDTO[] | null;
}
export interface AuthtypesDeprecatedPostableUserRoleDTO {
/**
* @type string
*/
id: string;
}
export interface AuthtypesGettableAuthDomainDTO {
authNProviderInfo?: AuthtypesAuthNProviderInfoDTO;
config?: AuthtypesAuthDomainConfigDTO;
@@ -2294,6 +2287,13 @@ export interface AuthtypesPostableRotateTokenDTO {
refreshToken?: string;
}
export interface AuthtypesPostableUserRoleDTO {
/**
* @type string
*/
id: string;
}
export interface AuthtypesPostableUserDTO {
/**
* @type string
@@ -2310,18 +2310,7 @@ export interface AuthtypesPostableUserDTO {
/**
* @type array
*/
userRoles?: AuthtypesDeprecatedPostableUserRoleDTO[];
}
export interface AuthtypesPostableUserRoleDTO {
/**
* @type string
*/
roleId: string;
/**
* @type string
*/
userId: string;
userRoles?: AuthtypesPostableUserRoleDTO[];
}
export interface AuthtypesRoleDTO {
@@ -5042,10 +5031,6 @@ export interface DashboardtypesListableDashboardForUserV2DTO {
* @type array
*/
dashboards: DashboardtypesListedDashboardForUserV2DTO[];
/**
* @type array
*/
reservedKeywords: string[];
/**
* @type array
*/
@@ -5113,10 +5098,6 @@ export interface DashboardtypesListableDashboardV2DTO {
* @type array
*/
dashboards: DashboardtypesListedDashboardV2DTO[];
/**
* @type array
*/
reservedKeywords: string[];
/**
* @type array
*/
@@ -8491,13 +8472,6 @@ export interface RuletypesRuleDTO {
export enum RuletypesThresholdKindDTO {
basic = 'basic',
}
export interface ServiceaccounttypesDeprecatedPostableServiceAccountRoleDTO {
/**
* @type string
*/
id: string;
}
export interface ServiceaccounttypesGettableFactorAPIKeyDTO {
/**
* @type string
@@ -8567,11 +8541,7 @@ export interface ServiceaccounttypesPostableServiceAccountRoleDTO {
/**
* @type string
*/
roleId: string;
/**
* @type string
*/
serviceAccountId: string;
id: string;
}
export interface ServiceaccounttypesServiceAccountDTO {
@@ -10329,28 +10299,6 @@ export type UpdateRoutePolicy200 = {
status: string;
};
export type CreateServiceAccountRole201 = {
data: TypesIdentifiableDTO;
/**
* @type string
*/
status: string;
};
export type DeleteServiceAccountRolePathParameters = {
id: string;
};
export type GetServiceAccountRolePathParameters = {
id: string;
};
export type GetServiceAccountRole200 = {
data: ServiceaccounttypesServiceAccountRoleDTO;
/**
* @type string
*/
status: string;
};
export type ListServiceAccounts200 = {
/**
* @type array
@@ -10434,10 +10382,10 @@ export type GetServiceAccountRoles200 = {
status: string;
};
export type CreateServiceAccountRoleDeprecatedPathParameters = {
export type CreateServiceAccountRolePathParameters = {
id: string;
};
export type CreateServiceAccountRoleDeprecated201 = {
export type CreateServiceAccountRole201 = {
data: TypesIdentifiableDTO;
/**
* @type string
@@ -10445,7 +10393,7 @@ export type CreateServiceAccountRoleDeprecated201 = {
status: string;
};
export type DeleteServiceAccountRoleDeprecatedPathParameters = {
export type DeleteServiceAccountRolePathParameters = {
id: string;
rid: string;
};
@@ -10610,28 +10558,6 @@ export type GetUserPreference200 = {
export type UpdateUserPreferencePathParameters = {
name: string;
};
export type CreateUserRole201 = {
data: TypesIdentifiableDTO;
/**
* @type string
*/
status: string;
};
export type DeleteUserRolePathParameters = {
id: string;
};
export type GetUserRolePathParameters = {
id: string;
};
export type GetUserRole200 = {
data: AuthtypesUserRoleDTO;
/**
* @type string
*/
status: string;
};
export type ListDashboardViews200 = {
data: DashboardtypesListableDashboardViewDTO;
/**

View File

@@ -19,15 +19,12 @@ import type {
import type {
AuthtypesPostableUserDTO,
AuthtypesPostableUserRoleDTO,
CreateInvite201,
CreateResetPasswordToken201,
CreateResetPasswordTokenPathParameters,
CreateUser201,
CreateUserRole201,
DeleteUserDeprecatedPathParameters,
DeleteUserPathParameters,
DeleteUserRolePathParameters,
GetMyUser200,
GetMyUserDeprecated200,
GetResetPasswordToken200,
@@ -40,8 +37,6 @@ import type {
GetUserDeprecated200,
GetUserDeprecatedPathParameters,
GetUserPathParameters,
GetUserRole200,
GetUserRolePathParameters,
GetUsersByRoleID200,
GetUsersByRoleIDPathParameters,
ListUsers200,
@@ -891,267 +886,6 @@ export const invalidateGetMyUserDeprecated = async (
return queryClient;
};
/**
* This endpoint assigns a role to a user
* @summary Create user role
*/
export const createUserRole = (
authtypesPostableUserRoleDTO?: BodyType<AuthtypesPostableUserRoleDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateUserRole201>({
url: `/api/v1/user_roles`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: authtypesPostableUserRoleDTO,
signal,
});
};
export const getCreateUserRoleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createUserRole>>,
TError,
{ data?: BodyType<AuthtypesPostableUserRoleDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createUserRole>>,
TError,
{ data?: BodyType<AuthtypesPostableUserRoleDTO> },
TContext
> => {
const mutationKey = ['createUserRole'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createUserRole>>,
{ data?: BodyType<AuthtypesPostableUserRoleDTO> }
> = (props) => {
const { data } = props ?? {};
return createUserRole(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateUserRoleMutationResult = NonNullable<
Awaited<ReturnType<typeof createUserRole>>
>;
export type CreateUserRoleMutationBody =
| BodyType<AuthtypesPostableUserRoleDTO>
| undefined;
export type CreateUserRoleMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create user role
*/
export const useCreateUserRole = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createUserRole>>,
TError,
{ data?: BodyType<AuthtypesPostableUserRoleDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createUserRole>>,
TError,
{ data?: BodyType<AuthtypesPostableUserRoleDTO> },
TContext
> => {
return useMutation(getCreateUserRoleMutationOptions(options));
};
/**
* This endpoint revokes a role from a user
* @summary Delete user role
*/
export const deleteUserRole = (
{ id }: DeleteUserRolePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/user_roles/${id}`,
method: 'DELETE',
signal,
});
};
export const getDeleteUserRoleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteUserRole>>,
TError,
{ pathParams: DeleteUserRolePathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteUserRole>>,
TError,
{ pathParams: DeleteUserRolePathParameters },
TContext
> => {
const mutationKey = ['deleteUserRole'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteUserRole>>,
{ pathParams: DeleteUserRolePathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteUserRole(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteUserRoleMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteUserRole>>
>;
export type DeleteUserRoleMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete user role
*/
export const useDeleteUserRole = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteUserRole>>,
TError,
{ pathParams: DeleteUserRolePathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteUserRole>>,
TError,
{ pathParams: DeleteUserRolePathParameters },
TContext
> => {
return useMutation(getDeleteUserRoleMutationOptions(options));
};
/**
* This endpoint gets an existing user role
* @summary Get user role
*/
export const getUserRole = (
{ id }: GetUserRolePathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetUserRole200>({
url: `/api/v1/user_roles/${id}`,
method: 'GET',
signal,
});
};
export const getGetUserRoleQueryKey = ({ id }: GetUserRolePathParameters) => {
return [`/api/v1/user_roles/${id}`] as const;
};
export const getGetUserRoleQueryOptions = <
TData = Awaited<ReturnType<typeof getUserRole>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetUserRolePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getUserRole>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getGetUserRoleQueryKey({ id });
const queryFn: QueryFunction<Awaited<ReturnType<typeof getUserRole>>> = ({
signal,
}) => getUserRole({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getUserRole>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetUserRoleQueryResult = NonNullable<
Awaited<ReturnType<typeof getUserRole>>
>;
export type GetUserRoleQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get user role
*/
export function useGetUserRole<
TData = Awaited<ReturnType<typeof getUserRole>>,
TError = ErrorType<RenderErrorResponseDTO>,
>(
{ id }: GetUserRolePathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getUserRole>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetUserRoleQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
return { ...query, queryKey: queryOptions.queryKey };
}
/**
* @summary Get user role
*/
export const invalidateGetUserRole = async (
queryClient: QueryClient,
{ id }: GetUserRolePathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetUserRoleQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint initiates the forgot password flow by sending a reset password email
* @summary Forgot password
@@ -2131,7 +1865,6 @@ export const invalidateGetRolesByUserID = async (
/**
* This endpoint assigns the role to the user roles by user id
* @deprecated
* @summary Set user roles
*/
export const setRoleByUserID = (
@@ -2203,7 +1936,6 @@ export type SetRoleByUserIDMutationBody =
export type SetRoleByUserIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @deprecated
* @summary Set user roles
*/
export const useSetRoleByUserID = <
@@ -2232,7 +1964,6 @@ export const useSetRoleByUserID = <
};
/**
* This endpoint removes a role from the user by user id and role id
* @deprecated
* @summary Remove a role from user
*/
export const removeUserRoleByUserIDAndRoleID = (
@@ -2291,7 +2022,6 @@ export type RemoveUserRoleByUserIDAndRoleIDMutationError =
ErrorType<RenderErrorResponseDTO>;
/**
* @deprecated
* @summary Remove a role from user
*/
export const useRemoveUserRoleByUserIDAndRoleID = <

View File

@@ -1,14 +1,11 @@
import { ReactElement } from 'react';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
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 { buildPermission } from 'hooks/useAuthZ/utils';
import type { AuthZObject, BrandedPermission } from 'hooks/useAuthZ/types';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import AuthZTooltip from './AuthZTooltip';
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
const noPermissions = {

View File

@@ -5,9 +5,9 @@ import {
TooltipProvider,
TooltipTrigger,
} from '@signozhq/ui/tooltip';
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 type { BrandedPermission } from 'hooks/useAuthZ/types';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { formatPermission } from 'hooks/useAuthZ/utils';
import { useAppContext } from 'providers/App/App';
import styles from './AuthZTooltip.module.scss';

View File

@@ -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 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import { SACreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import { SACreatePermission } from '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';

View File

@@ -11,7 +11,7 @@ import {
import CreateServiceAccountModal from '../CreateServiceAccountModal';
jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
jest.mock('components/AuthZTooltip/AuthZTooltip', () => ({
__esModule: true,
default: ({
children,

View File

@@ -1,14 +1,5 @@
import { MutableRefObject } from 'react';
import {
ActiveElement,
Chart,
ChartConfiguration,
ChartData,
ChartEvent,
ChartType,
Color,
TooltipItem,
} from 'chart.js';
import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js';
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
@@ -69,189 +60,184 @@ export const getGraphOptions = (
minTime?: number,
maxTime?: number,
// eslint-disable-next-line sonarjs/cognitive-complexity
): CustomChartOptions =>
({
animation: {
duration: animate ? 200 : 0,
},
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: true,
},
plugins: {
...(staticLine
? {
annotation: {
annotations: [
{
type: 'line',
yMin: staticLine.yMin,
yMax: staticLine.yMax,
borderColor: staticLine.borderColor,
borderWidth: staticLine.borderWidth,
label: {
content: staticLine.lineText,
enabled: true,
font: {
size: 10,
},
borderWidth: 0,
position: 'start',
backgroundColor: 'transparent',
color: staticLine.textColor,
): CustomChartOptions => ({
animation: {
duration: animate ? 200 : 0,
},
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: true,
},
plugins: {
...(staticLine
? {
annotation: {
annotations: [
{
type: 'line',
yMin: staticLine.yMin,
yMax: staticLine.yMax,
borderColor: staticLine.borderColor,
borderWidth: staticLine.borderWidth,
label: {
content: staticLine.lineText,
enabled: true,
font: {
size: 10,
},
borderWidth: 0,
position: 'start',
backgroundColor: 'transparent',
color: staticLine.textColor,
},
],
},
}
: {}),
title: {
display: title !== undefined,
text: title,
},
legend: {
display: false,
},
tooltip: {
callbacks: {
title(context: TooltipItem<'line'>[]): string | string[] {
const date = dayjs(context[0].parsed.x);
return date
.tz(timezone.value)
.format(DATE_TIME_FORMATS.MONTH_DATETIME_FULL_SECONDS);
},
],
},
label(context: TooltipItem<'line'>): string | string[] {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += getToolTipValue(context.parsed.y.toString(), yAxisUnit);
}
return label;
},
labelTextColor(labelData: TooltipItem<'line'>): Color {
if (labelData.datasetIndex === nearestDatasetIndex.current) {
return 'rgba(255, 255, 255, 1)';
}
return 'rgba(255, 255, 255, 0.75)';
},
},
position: 'custom',
itemSort(item1: TooltipItem<'line'>, item2: TooltipItem<'line'>): number {
return item2.parsed.y - item1.parsed.y;
},
},
[dragSelectPluginId]: createDragSelectPluginOptions(
!!onDragSelect,
onDragSelect,
dragSelectColor,
),
[intersectionCursorPluginId]: createIntersectionCursorPluginOptions(
!!onDragSelect,
currentTheme === 'dark' ? 'white' : 'black',
),
},
layout: {
padding: 0,
},
scales: {
x: {
stacked: isStacked,
offset: false,
grid: {
display: true,
color: getGridColor(),
drawTicks: true,
},
adapters: {
date: chartjsAdapter,
},
time: {
unit: xAxisTimeUnit?.unitName || 'minute',
stepSize: xAxisTimeUnit?.stepSize || 1,
displayFormats: {
millisecond: DATE_TIME_FORMATS.TIME_SECONDS,
second: DATE_TIME_FORMATS.TIME_SECONDS,
minute: DATE_TIME_FORMATS.TIME,
hour: DATE_TIME_FORMATS.SLASH_SHORT,
day: DATE_TIME_FORMATS.DATE_SHORT,
week: DATE_TIME_FORMATS.DATE_SHORT,
month: DATE_TIME_FORMATS.YEAR_MONTH,
year: DATE_TIME_FORMATS.YEAR_SHORT,
},
},
type: 'time',
ticks: { color: getAxisLabelColor(currentTheme) },
...(minTime && {
min: dayjs(minTime).tz(timezone.value).format(),
}),
...(maxTime && {
max: dayjs(maxTime).tz(timezone.value).format(),
}),
},
y: {
stacked: isStacked,
display: true,
grid: {
display: true,
color: getGridColor(),
},
ticks: {
color: getAxisLabelColor(currentTheme),
// Include a dollar sign in the ticks
callback(value: number | string): string {
return getYAxisFormattedValue(value.toString(), yAxisUnit);
},
},
},
},
elements: {
line: {
tension: 0,
cubicInterpolationMode: 'monotone',
},
point: {
hoverBackgroundColor: (ctx: any): string => {
if (ctx?.element?.options?.borderColor) {
return ctx.element.options.borderColor;
}
return 'rgba(0,0,0,0.1)';
},
hoverRadius: 5,
},
},
onClick: (
event: ChartEvent,
element: ActiveElement[],
chart: Chart,
): void => {
if (onClickHandler) {
onClickHandler(event, element, chart, data);
}
},
onHover: (event: ChartEvent, _: ActiveElement[], chart: Chart): void => {
if (event.native) {
const interactions = chart.getElementsAtEventForMode(
event.native,
'nearest',
{
intersect: false,
},
true,
);
if (interactions[0]) {
nearestDatasetIndex.current = interactions[0].datasetIndex;
}
}
: {}),
title: {
display: title !== undefined,
text: title,
},
}) as CustomChartOptions;
legend: {
display: false,
},
tooltip: {
callbacks: {
title(context): string | string[] {
const date = dayjs(context[0].parsed.x);
return date
.tz(timezone.value)
.format(DATE_TIME_FORMATS.MONTH_DATETIME_FULL_SECONDS);
},
label(context): string | string[] {
let label = context.dataset.label || '';
if (label) {
label += ': ';
}
if (context.parsed.y !== null) {
label += getToolTipValue(context.parsed.y.toString(), yAxisUnit);
}
return label;
},
labelTextColor(labelData): Color {
if (labelData.datasetIndex === nearestDatasetIndex.current) {
return 'rgba(255, 255, 255, 1)';
}
return 'rgba(255, 255, 255, 0.75)';
},
},
position: 'custom',
itemSort(item1, item2): number {
return item2.parsed.y - item1.parsed.y;
},
},
[dragSelectPluginId]: createDragSelectPluginOptions(
!!onDragSelect,
onDragSelect,
dragSelectColor,
),
[intersectionCursorPluginId]: createIntersectionCursorPluginOptions(
!!onDragSelect,
currentTheme === 'dark' ? 'white' : 'black',
),
},
layout: {
padding: 0,
},
scales: {
x: {
stacked: isStacked,
offset: false,
grid: {
display: true,
color: getGridColor(),
drawTicks: true,
},
adapters: {
date: chartjsAdapter,
},
time: {
unit: xAxisTimeUnit?.unitName || 'minute',
stepSize: xAxisTimeUnit?.stepSize || 1,
displayFormats: {
millisecond: DATE_TIME_FORMATS.TIME_SECONDS,
second: DATE_TIME_FORMATS.TIME_SECONDS,
minute: DATE_TIME_FORMATS.TIME,
hour: DATE_TIME_FORMATS.SLASH_SHORT,
day: DATE_TIME_FORMATS.DATE_SHORT,
week: DATE_TIME_FORMATS.DATE_SHORT,
month: DATE_TIME_FORMATS.YEAR_MONTH,
year: DATE_TIME_FORMATS.YEAR_SHORT,
},
},
type: 'time',
ticks: { color: getAxisLabelColor(currentTheme) },
...(minTime && {
min: dayjs(minTime).tz(timezone.value).format(),
}),
...(maxTime && {
max: dayjs(maxTime).tz(timezone.value).format(),
}),
},
y: {
stacked: isStacked,
display: true,
grid: {
display: true,
color: getGridColor(),
},
ticks: {
color: getAxisLabelColor(currentTheme),
// Include a dollar sign in the ticks
callback(value): string {
return getYAxisFormattedValue(value.toString(), yAxisUnit);
},
},
},
},
elements: {
line: {
tension: 0,
cubicInterpolationMode: 'monotone',
},
point: {
hoverBackgroundColor: (ctx: any): string => {
if (ctx?.element?.options?.borderColor) {
return ctx.element.options.borderColor;
}
return 'rgba(0,0,0,0.1)';
},
hoverRadius: 5,
},
},
onClick: (event, element, chart): void => {
if (onClickHandler) {
onClickHandler(event, element, chart, data);
}
},
onHover: (event, _, chart): void => {
if (event.native) {
const interactions = chart.getElementsAtEventForMode(
event.native,
'nearest',
{
intersect: false,
},
true,
);
if (interactions[0]) {
nearestDatasetIndex.current = interactions[0].datasetIndex;
}
}
},
});
declare module 'chart.js' {
interface TooltipPositionerMap {

View File

@@ -1,13 +1,10 @@
import { ReactElement } from 'react';
import { BrandedPermission } from 'lib/authz/hooks/useAuthZ/types';
import { buildPermission } from 'lib/authz/hooks/useAuthZ/utils';
import { BrandedPermission } from 'hooks/useAuthZ/types';
import { buildPermission } from '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 'lib/authz/utils/authz-test-utils';
import { AUTHZ_CHECK_URL, authzMockResponse } from 'tests/authz-test-utils';
import { GuardAuthZ } from './GuardAuthZ';

View File

@@ -3,9 +3,9 @@ import {
AuthZObject,
AuthZRelation,
BrandedPermission,
} from 'lib/authz/hooks/useAuthZ/types';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
import { buildPermission } from 'lib/authz/hooks/useAuthZ/utils';
} from 'hooks/useAuthZ/types';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { buildPermission } from 'hooks/useAuthZ/utils';
export type GuardAuthZProps<R extends AuthZRelation> = {
children: ReactElement;

View File

@@ -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 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
APIKeyCreatePermission,
buildSAAttachPermission,
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { popupContainer } from 'utils/selectPopupContainer';
import { disabledDate } from '../utils';

View File

@@ -1,8 +1,8 @@
import { useQueryClient } from 'react-query';
import { Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import { buildSADeletePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import { buildSADeletePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';

View File

@@ -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 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
buildAPIKeyDeletePermission,
buildAPIKeyUpdatePermission,
buildSADetachPermission,
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { popupContainer } from 'utils/selectPopupContainer';
import { disabledDate, formatLastObservedAt } from '../utils';

View File

@@ -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 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
import { buildAPIKeyUpdatePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { parseAsString, useQueryState } from 'nuqs';
import { useErrorModal } from 'providers/ErrorModalProvider';
import { useTimezone } from 'providers/Timezone';

View File

@@ -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 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
APIKeyCreatePermission,
buildAPIKeyDeletePermission,
buildSAAttachPermission,
buildSADetachPermission,
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import dayjs from 'dayjs';
import { parseAsBoolean, parseAsString, useQueryState } from 'nuqs';

View File

@@ -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 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import RolesSelect from 'components/RolesSelect';
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
import { ServiceAccountRow } from 'container/ServiceAccountsSettings/utils';
import { buildSAUpdatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { buildSAUpdatePermission } from 'hooks/useAuthZ/permissions/service-account.permissions';
import { useTimezone } from 'providers/Timezone';
import APIError from 'types/api/error';

View File

@@ -1,11 +1,11 @@
import { useQueryClient } from 'react-query';
import { Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import AuthZTooltip from 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import {
buildAPIKeyDeletePermission,
buildSADetachPermission,
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { toast } from '@signozhq/ui/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';

View File

@@ -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 'lib/authz/components/PermissionDeniedCallout/PermissionDeniedCallout';
import PermissionDeniedCallout from '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 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { useAuthZ } from '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 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import AddKeyModal from './AddKeyModal';
import DeleteAccountModal from './DeleteAccountModal';
import KeysTab from './KeysTab';

View File

@@ -6,7 +6,7 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import EditKeyModal from '../EditKeyModal';
jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
jest.mock('components/AuthZTooltip/AuthZTooltip', () => ({
__esModule: true,
default: ({
children,

View File

@@ -6,7 +6,7 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import KeysTab from '../KeysTab';
jest.mock('lib/authz/components/AuthZTooltip/AuthZTooltip', () => ({
jest.mock('components/AuthZTooltip/AuthZTooltip', () => ({
__esModule: true,
default: ({
children,

View File

@@ -7,11 +7,11 @@ import {
setupAuthzAdmin,
setupAuthzDeny,
setupAuthzDenyAll,
} from 'lib/authz/utils/authz-test-utils';
} from 'tests/authz-test-utils';
import {
APIKeyListPermission,
buildSADeletePermission,
} from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import ServiceAccountDrawer from '../ServiceAccountDrawer';

View File

@@ -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 'lib/authz/utils/authz-test-utils';
import { setupAuthzAdmin } from 'tests/authz-test-utils';
import ServiceAccountDrawer from '../ServiceAccountDrawer';

View File

@@ -7,10 +7,7 @@ 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 'lib/authz/utils/authz-test-utils';
import { AUTHZ_CHECK_URL, authzMockResponse } from 'tests/authz-test-utils';
import { createGuardedRoute } from './createGuardedRoute';

View File

@@ -4,13 +4,13 @@ import {
AuthZObject,
AuthZRelation,
BrandedPermission,
} from 'lib/authz/hooks/useAuthZ/types';
import { formatPermission } from 'lib/authz/hooks/useAuthZ/utils';
} from 'hooks/useAuthZ/types';
import { formatPermission } from '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 '../../../../components/AppLoading/AppLoading';
import AppLoading from '../AppLoading/AppLoading';
import { GuardAuthZ } from '../GuardAuthZ/GuardAuthZ';
import './createGuardedRoute.styles.scss';

View File

@@ -2,9 +2,8 @@ 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 {
@@ -14,13 +13,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 { useTimezone } from 'providers/Timezone';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { AlignedData, Options } from 'uplot';
import { buildEntityMetricsChartConfig } from './configBuilder';
import { useMultiIntersectionObserver } from 'hooks/useMultiIntersectionObserver';
import { useEntityMetrics } from './hooks';
import { isKeyNotFoundError } from '../utils';
@@ -71,7 +70,7 @@ function EntityMetrics<T>({
{ threshold: 0.1 },
);
const { queries, chartData, tableData, queryPayloads } = useEntityMetrics({
const { queries, chartData, queryPayloads } = useEntityMetrics({
queryKey,
timeRange,
entity,
@@ -81,10 +80,16 @@ 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 => {
@@ -96,39 +101,43 @@ function EntityMetrics<T>({
[handleTimeChange],
);
const configs = useMemo(
const options = useMemo(
() =>
queries.map(({ data }, idx) => {
const panelType = queryPayloads[idx]?.graphType;
if (panelType === PANEL_TYPES.TABLE) {
return null;
}
const widgetTitle = entityWidgetInfo[idx].title
.toLowerCase()
.replace(/\s+/g, '-');
return buildEntityMetricsChartConfig({
id: `${category}-${widgetTitle}`,
isDarkMode,
currentQuery,
onDragSelect,
return getUPlotChartOptions({
apiResponse: data?.payload,
timezone,
isDarkMode,
dimensions,
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,
currentQuery,
onDragSelect,
timezone,
dimensions,
entityWidgetInfo,
timeRange.startTime,
timeRange.endTime,
onDragSelect,
currentQuery,
],
);
@@ -161,22 +170,14 @@ function EntityMetrics<T>({
>
{panelType === PANEL_TYPES.TABLE ? (
<MetricsTable
rows={tableData[idx]?.[0]?.rows ?? []}
columns={tableData[idx]?.[0]?.columns ?? []}
rows={chartData[idx]?.[0]?.rows ?? []}
columns={chartData[idx]?.[0]?.columns ?? []}
/>
) : (
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}
/>
)
<Uplot
options={options[idx] as Options}
data={chartData[idx] as AlignedData}
/>
)}
</div>
);

View File

@@ -3,7 +3,6 @@ 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';
@@ -16,15 +15,12 @@ const mockUseEntityMetrics = useEntityMetrics as jest.MockedFunction<
typeof useEntityMetrics
>;
jest.mock('../configBuilder', () => ({
buildEntityMetricsChartConfig: jest.fn().mockReturnValue({
getId: jest.fn().mockReturnValue('mock-id'),
}),
jest.mock('lib/uPlotLib/getUplotChartOptions', () => ({
getUPlotChartOptions: jest.fn().mockReturnValue({}),
}));
jest.mock('lib/uPlotV2/utils/dataUtils', () => ({
prepareChartData: jest.fn().mockReturnValue([]),
hasSingleVisiblePoint: jest.fn().mockReturnValue(false),
jest.mock('lib/uPlotLib/utils/getUplotChartData', () => ({
getUPlotChartData: jest.fn().mockReturnValue([]),
}));
jest.mock('container/TopNav/DateTimeSelectionV2', () => ({
@@ -34,20 +30,9 @@ jest.mock('container/TopNav/DateTimeSelectionV2', () => ({
),
}));
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('components/Uplot', () => ({
__esModule: true,
default: (): JSX.Element => <div data-testid="uplot-chart">Uplot Chart</div>,
}));
jest.mock('../MetricsTable', () => ({
@@ -309,28 +294,20 @@ const renderEntityMetrics = (overrides = {}): any => {
);
};
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
const mockChartData = [
[], // time_series chart data (uplot handles empty array)
[
{
rows: [
{ timestamp: '2024-01-15T10:00:00Z', value: '1024' },
{ timestamp: '2024-01-15T10:01:00Z', value: '1028' },
{ data: { timestamp: '2024-01-15T10:00:00Z', value: '1024' } },
{ data: { timestamp: '2024-01-15T10:01:00Z', value: '1028' } },
],
columns: [
{ key: 'timestamp', label: 'Timestamp', isValueColumn: false },
{ key: 'value', label: 'Value', isValueColumn: true },
],
},
], // table data
], // table chart data
];
const mockQueryPayloads = [
@@ -344,7 +321,6 @@ describe('EntityMetrics', () => {
mockUseEntityMetrics.mockReturnValue({
queries: mockQueries as any,
chartData: mockChartData,
tableData: mockTableData,
queryPayloads: mockQueryPayloads as any,
});
mockUseQuery.mockReturnValue({
@@ -375,8 +351,7 @@ describe('EntityMetrics', () => {
it('renders loading state when fetching metrics', () => {
mockUseEntityMetrics.mockReturnValue({
queries: mockLoadingQueries as any,
chartData: [null, null],
tableData: [null, null],
chartData: [[], []],
queryPayloads: mockQueryPayloads as any,
});
renderEntityMetrics();
@@ -387,8 +362,7 @@ describe('EntityMetrics', () => {
it('renders error state when query fails', () => {
mockUseEntityMetrics.mockReturnValue({
queries: mockErrorQueries as any,
chartData: [null, null],
tableData: [null, null],
chartData: [[], []],
queryPayloads: mockQueryPayloads as any,
});
renderEntityMetrics();
@@ -399,9 +373,8 @@ describe('EntityMetrics', () => {
it('renders empty state when no metrics data', () => {
mockUseEntityMetrics.mockReturnValue({
queries: mockEmptyQueries as any,
chartData: [[[]], null],
tableData: [
null,
chartData: [
[],
[
{
rows: [],

View File

@@ -1,122 +0,0 @@
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;
}

View File

@@ -8,14 +8,13 @@ import {
GetMetricQueryRange,
GetQueryResultsProps,
} from 'lib/dashboard/getQueryResults';
import { prepareChartData } from 'lib/uPlotV2/utils/dataUtils';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
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, MetricsTableData } from './utils';
import { FeatureKeys } from '../../../../constants/features';
import { useAppContext } from '../../../../providers/App/App';
import { getMetricsTableData } from './utils';
export interface UseEntityMetricsParams<T> {
queryKey: string;
@@ -33,8 +32,10 @@ export interface UseEntityMetricsParams<T> {
export interface UseEntityMetricsResult {
queries: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>[];
chartData: (uPlot.AlignedData | null)[];
tableData: (MetricsTableData[] | null)[];
chartData: (
| ReturnType<typeof getUPlotChartData>
| ReturnType<typeof getMetricsTableData>
)[];
queryPayloads: GetQueryResultsProps[];
}
@@ -92,22 +93,9 @@ export function useEntityMetrics<T>({
() =>
queries.map(({ data }, index) => {
const panelType = queryPayloads[index]?.graphType;
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);
return panelType === PANEL_TYPES.TABLE
? getMetricsTableData(data)
: getUPlotChartData(data?.payload);
}),
[queries, queryPayloads],
);
@@ -115,7 +103,6 @@ export function useEntityMetrics<T>({
return {
queries,
chartData,
tableData,
queryPayloads,
};
}

View File

@@ -1,8 +0,0 @@
.emptyMeterSearch {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}

View File

@@ -1,26 +0,0 @@
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>
);
}

View File

@@ -73,6 +73,34 @@
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;
}
}
}
@@ -85,6 +113,22 @@
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;

View File

@@ -35,6 +35,7 @@ function Explorer(): JSX.Element {
handleRunQuery,
stagedQuery,
updateAllQueriesOperators,
handleSetQueryData,
currentQuery,
} = useQueryBuilder();
const { safeNavigate } = useSafeNavigate();
@@ -66,6 +67,15 @@ 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(

View File

@@ -1,18 +0,0 @@
.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));
}

View File

@@ -1,17 +0,0 @@
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>
);
}

View File

@@ -1,31 +0,0 @@
.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);
}

View File

@@ -1,28 +1,27 @@
import { useMemo, useRef } from 'react';
import { useEffect, useMemo } from 'react';
import { useQueries } from 'react-query';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { isAxiosError } from 'axios';
import QueryCancelledPlaceholder from 'components/QueryCancelledPlaceholder';
import BarChart from 'container/DashboardContainer/visualization/charts/BarChart/BarChart';
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 { 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 { LegendPosition } from 'lib/uPlotV2/components/types';
import { prepareChartData } from 'lib/uPlotV2/utils/dataUtils';
import { useTimezone } from 'providers/Timezone';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { useErrorModal } from 'providers/ErrorModalProvider';
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;
@@ -33,124 +32,144 @@ 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 { minTimeScale, maxTimeScale, onDragSelect } =
useTimeSeriesTimeManagement({
globalSelectedTime,
maxTime,
minTime,
});
const isValidToConvertToMs = useMemo(() => {
const isValid: boolean[] = [];
const { responseData, isLoading, isError } = useTimeSeriesQueries({
stagedQuery,
currentQuery,
globalSelectedTime,
maxTime,
minTime,
onFetchingStateChange,
});
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 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={styles.meterTimeSeriesContainer}>
<div className="meter-time-series-container">
<BuilderUnitsFilter onChange={onUnitChange} yAxisUnit={yAxisUnit} />
<div className={styles.timeSeriesContainer} ref={graphRef}>
{!hasMetricSelected && <EmptyMeterSearch />}
<div className="time-series-container">
{!hasMetricSelected && <EmptyMetricsSearch />}
{isCancelled && hasMetricSelected && (
<QueryCancelledPlaceholder subText='Click "Run Query" to load metrics.' />
)}
{isLoading && hasMetricSelected && !isCancelled && <MeterLoading />}
{!isCancelled &&
hasMetricSelected &&
!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>
),
)}
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>
))}
</div>
</div>
);

View File

@@ -1,117 +0,0 @@
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;
}

View File

@@ -1,146 +0,0 @@
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,
};
}

View File

@@ -1,102 +0,0 @@
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,
};
}

View File

@@ -1,13 +0,0 @@
.autoRefresh {
display: flex;
align-items: center;
gap: 6px;
}
.icon {
color: var(--l2-foreground);
}
.select {
min-width: 96px;
}

View File

@@ -1,41 +0,0 @@
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;

View File

@@ -1,12 +1,10 @@
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 {
@@ -16,14 +14,12 @@ 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';
@@ -125,34 +121,14 @@ function PublicDashboardContainer({
const { maxTime, minTime } = GetMinMax(interval);
setSelectedTimeRange({
startTime: Math.floor(minTime / NANO_SECOND_MULTIPLIER / 1000),
endTime: Math.floor(maxTime / NANO_SECOND_MULTIPLIER / 1000),
startTime: Math.floor(minTime / 1000000000),
endTime: Math.floor(maxTime / 1000000000),
});
}
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">
@@ -172,11 +148,6 @@ function PublicDashboardContainer({
{isTimeRangeEnabled && (
<div className="public-dashboard-header-right">
<AutoRefresh
value={refreshIntervalKey}
disabled={isAutoRefreshPaused}
onChange={setRefreshIntervalKey}
/>
<div className="datetime-section">
<DateTimeSelectionV2
showAutoRefresh={false}

View File

@@ -1,23 +0,0 @@
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');
});
});

View File

@@ -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 'lib/authz/components/PermissionDeniedFullPage/PermissionDeniedFullPage';
import PermissionDeniedFullPage from 'components/PermissionDeniedFullPage/PermissionDeniedFullPage';
import ROUTES from 'constants/routes';
import { useRolesFeatureGate } from 'hooks/useRolesFeatureGate';
import useUrlQuery from 'hooks/useUrlQuery';

View File

@@ -1,16 +1,13 @@
import { Route, Switch } from 'react-router-dom';
import ROUTES from 'constants/routes';
import { FeatureKeys } from 'constants/features';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { defaultFeatureFlags, render, screen } from 'tests/test-utils';
import {
invalidLicense,
mockUseAuthZGrantAll,
} from 'lib/authz/utils/authz-test-utils';
import { invalidLicense, mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import CreateEditRolePage from '../CreateEditRolePage';
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
beforeEach(() => {

View File

@@ -1,12 +1,12 @@
import { Route, Switch } from 'react-router-dom';
import ROUTES from 'constants/routes';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { render, screen } from 'tests/test-utils';
import { mockUseAuthZDenyAll } from 'lib/authz/utils/authz-test-utils';
import { mockUseAuthZDenyAll } from 'tests/authz-test-utils';
import CreateEditRolePage from '../CreateEditRolePage';
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
afterEach(() => {

View File

@@ -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 'lib/authz/hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import CreateEditRolePage from '../CreateEditRolePage';
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
const rolesApiBase = '*/api/v1/roles';

View File

@@ -1,15 +1,15 @@
import { Route, Switch } from 'react-router-dom';
import ROUTES from 'constants/routes';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { render, screen } from 'tests/test-utils';
import {
mockUseAuthZDenyAll,
mockUseAuthZGrantByPrefix,
} from 'lib/authz/utils/authz-test-utils';
} from 'tests/authz-test-utils';
import CreateEditRolePage from '../CreateEditRolePage';
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
const EDIT_ROLE_ID = 'test-role-123';

View File

@@ -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 'lib/authz/hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import CreateEditRolePage from '../CreateEditRolePage';
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
const CUSTOM_ROLE_ID = '019c24aa-3333-0001-aaaa-111111111111';

View File

@@ -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 'lib/authz/hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import CreateEditRolePage from '../CreateEditRolePage';
import { TooltipProvider } from '@signozhq/ui/tooltip';
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
beforeEach(() => {

View File

@@ -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 'lib/authz/hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import { TooltipProvider } from '@signozhq/ui/tooltip';
import CreateEditRolePage from '../CreateEditRolePage';
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
async function expandAllCards(): Promise<void> {

View File

@@ -9,7 +9,7 @@ import { getResourcePanel } from '../../permissions.config';
import ItemInputSelector from './ItemInputSelector';
import styles from './ActionToggle.module.scss';
import { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
import { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
import { getActionLabel } from 'container/RolesSettings/ViewRolePage/components/permissionDisplay.utils';
const SCOPE_LABELS: Record<PermissionScope, string> = {

View File

@@ -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 'lib/authz/hooks/useAuthZ/types';
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
import { PermissionScope, ResourcePermissions } from '../../types';
import type { EditorMode, JsonEditorRef } from './JsonEditor.types';

View File

@@ -1,6 +1,6 @@
import { useCallback, useMemo, useState } from 'react';
import { ChevronDown, ChevronRight } from '@signozhq/icons';
import type { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
import { Typography } from '@signozhq/ui/typography';

View File

@@ -1,5 +1,5 @@
import type { Monaco } from '@monaco-editor/react';
import permissionsType from 'lib/authz/hooks/useAuthZ/permissions.type';
import permissionsType from 'hooks/useAuthZ/permissions.type';
import transactionGroupSchema from 'schemas/generated/transactionGroups.schema.json';
export const TRANSACTION_GROUP_SCHEMA = transactionGroupSchema;

View File

@@ -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 'lib/authz/components/PermissionDeniedFullPage/PermissionDeniedFullPage';
import PermissionDeniedFullPage from 'components/PermissionDeniedFullPage/PermissionDeniedFullPage';
import ROUTES from 'constants/routes';
import { RoleListPermission } from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
import { RoleListPermission } from 'hooks/useAuthZ/permissions/role.permissions';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { useRolesFeatureGate } from 'hooks/useRolesFeatureGate';
import useUrlQuery from 'hooks/useUrlQuery';
import LineClampedText from 'periscope/components/LineClampedText/LineClampedText';

View File

@@ -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 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import ROUTES from 'constants/routes';
import { RoleCreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/role.permissions';
import { RoleCreatePermission } from 'hooks/useAuthZ/permissions/role.permissions';
import { useRolesFeatureGate } from 'hooks/useRolesFeatureGate';
import RolesListingTable from './RolesComponents/RolesListingTable';

View File

@@ -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 'lib/authz/components/PermissionDeniedFullPage/PermissionDeniedFullPage';
import PermissionDeniedFullPage from 'components/PermissionDeniedFullPage/PermissionDeniedFullPage';
import { useDeleteRoleModal } from 'container/RolesSettings/DeleteRoleModal/useDeleteRoleModal';
import { useRoleAuthZ } from 'container/RolesSettings/hooks/useRoleAuthZ';
import { transformApiToRolePermissions } from 'container/RolesSettings/hooks/useRolePermissions';

View File

@@ -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 'lib/authz/hooks/useAuthZ/useAuthZ';
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
import {
customRoleResponse,
managedRoleResponse,
@@ -10,7 +10,7 @@ import {
mockUseAuthZDenyAll,
mockUseAuthZGrantAll,
mockUseAuthZGrantByPrefix,
} from 'lib/authz/utils/authz-test-utils';
} from 'tests/authz-test-utils';
import { render, screen, waitFor } from 'tests/test-utils';
import * as useRolePermissionsModule from '../../hooks/useRolePermissions';

View File

@@ -1,7 +1,7 @@
import * as roleApi from 'api/generated/services/role';
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
import { customRoleResponse } from 'mocks-server/__mockdata__/roles';
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import { render, screen } from 'tests/test-utils';
import * as useRolePermissionsModule from '../../hooks/useRolePermissions';

View File

@@ -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 'lib/authz/hooks/useAuthZ/useAuthZ';
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
import { customRoleResponse } from 'mocks-server/__mockdata__/roles';
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import { render, screen } from 'tests/test-utils';
import * as useRolePermissionsModule from '../../hooks/useRolePermissions';

View File

@@ -1,11 +1,8 @@
import * as roleApi from 'api/generated/services/role';
import { FeatureKeys } from 'constants/features';
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
import { defaultFeatureFlags, render, screen } from 'tests/test-utils';
import {
invalidLicense,
mockUseAuthZGrantAll,
} from 'lib/authz/utils/authz-test-utils';
import { invalidLicense, mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import ViewRolePage from '../ViewRolePage';

View File

@@ -1,6 +1,6 @@
import * as roleApi from 'api/generated/services/role';
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import { render } from 'tests/test-utils';
import ViewRolePage from '../ViewRolePage';

View File

@@ -1,7 +1,7 @@
import * as roleApi from 'api/generated/services/role';
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
import { customRoleResponse } from 'mocks-server/__mockdata__/roles';
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import userEvent from '@testing-library/user-event';
import { render, screen, within } from 'tests/test-utils';

View File

@@ -3,12 +3,12 @@ import {
CoretypesTypeDTO,
} from 'api/generated/services/sigNoz.schemas';
import * as roleApi from 'api/generated/services/role';
import * as useAuthZModule from 'lib/authz/hooks/useAuthZ/useAuthZ';
import * as useAuthZModule from 'hooks/useAuthZ/useAuthZ';
import {
customRoleResponse,
managedRoleResponse,
} from 'mocks-server/__mockdata__/roles';
import { mockUseAuthZGrantAll } from 'lib/authz/utils/authz-test-utils';
import { mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import * as useRolePermissionsModule from '../../hooks/useRolePermissions';

View File

@@ -11,15 +11,12 @@ import {
userEvent,
} from 'tests/test-utils';
import { FeatureKeys } from 'constants/features';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
import {
invalidLicense,
mockUseAuthZGrantAll,
} from 'lib/authz/utils/authz-test-utils';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { invalidLicense, mockUseAuthZGrantAll } from 'tests/authz-test-utils';
import RolesSettings from '../RolesSettings';
jest.mock('lib/authz/hooks/useAuthZ/useAuthZ');
jest.mock('hooks/useAuthZ/useAuthZ');
const mockUseAuthZ = useAuthZ as jest.MockedFunction<typeof useAuthZ>;
const rolesApiURL = 'http://localhost/api/v1/roles';

View File

@@ -4,7 +4,7 @@ import {
CoretypesKindDTO,
CoretypesTypeDTO,
} from 'api/generated/services/sigNoz.schemas';
import type { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
import {
ActionConfig,

View File

@@ -4,12 +4,9 @@ import {
buildRoleReadPermission,
buildRoleUpdatePermission,
RoleCreatePermission,
} 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';
} from 'hooks/useAuthZ/permissions/role.permissions';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { ParsedPermissionObject, parsePermission } from 'hooks/useAuthZ/utils';
interface UseRoleAuthZResult {
readRolePermission: ParsedPermissionObject;

View File

@@ -18,7 +18,7 @@ import {
useGetRole,
useUpdateRole,
} from 'api/generated/services/role';
import type { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
import {
getResourcePanel,

View File

@@ -1,12 +1,12 @@
import { Bot, Key, Shield } from '@signozhq/icons';
import permissionsType from 'lib/authz/hooks/useAuthZ/permissions.type';
import permissionsType from 'hooks/useAuthZ/permissions.type';
import {
AuthZResource,
AuthZVerb,
OBJECT_SCOPED_VERBS,
ObjectScopedVerb,
} from 'lib/authz/hooks/useAuthZ/types';
} from '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/lib/authz/hooks/useAuthZ/legacy.ts
// TODO(H4ad): Remove this once we get rid of frontend/src/hooks/useAuthZ/legacy.ts
if (resource === 'role') {
return match.allowedVerbs.filter((verb) => verb !== 'assignee');
}

View File

@@ -1,4 +1,4 @@
import type { AuthZResource, AuthZVerb } from 'lib/authz/hooks/useAuthZ/types';
import type { AuthZResource, AuthZVerb } from 'hooks/useAuthZ/types';
import { CoretypesTypeDTO } from 'api/generated/services/sigNoz.schemas';
export enum PermissionScope {

View File

@@ -3,10 +3,7 @@ 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 'lib/authz/utils/authz-test-utils';
import { AUTHZ_CHECK_URL, authzMockResponse } from 'tests/authz-test-utils';
import ServiceAccountsSettings from './ServiceAccountsSettings';
const SA_LIST_URL = 'http://localhost/api/v1/service_accounts';

View File

@@ -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 'lib/authz/components/AuthZTooltip/AuthZTooltip';
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
import CreateServiceAccountModal from 'components/CreateServiceAccountModal/CreateServiceAccountModal';
import ErrorInPlace from 'components/ErrorInPlace/ErrorInPlace';
import PermissionDeniedFullPage from 'lib/authz/components/PermissionDeniedFullPage/PermissionDeniedFullPage';
import PermissionDeniedFullPage from '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 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { useAuthZ } from 'lib/authz/hooks/useAuthZ/useAuthZ';
} from 'hooks/useAuthZ/permissions/service-account.permissions';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import {
parseAsBoolean,
parseAsInteger,

View File

@@ -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 'lib/authz/utils/authz-test-utils';
import { setupAuthzAdmin } from 'tests/authz-test-utils';
import ServiceAccountsSettings from '../ServiceAccountsSettings';

View File

@@ -30,7 +30,6 @@ 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';
@@ -58,7 +57,6 @@ function TimeSeriesView({
dataSource,
setWarning,
panelType = PANEL_TYPES.TIME_SERIES,
stackBarChart = false,
}: TimeSeriesViewProps): JSX.Element {
const graphRef = useRef<HTMLDivElement>(null);
@@ -67,23 +65,11 @@ function TimeSeriesView({
const location = useLocation();
const { currentQuery } = useQueryBuilder();
const rawChartData = useMemo(
const chartData = 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);
@@ -203,7 +189,7 @@ function TimeSeriesView({
const { timezone } = useTimezone();
const baseChartOptions = getUPlotChartOptions({
const chartOptions = getUPlotChartOptions({
id: 'time-series-explorer',
onDragSelect,
yAxisUnit: yAxisUnit || '',
@@ -236,14 +222,6 @@ 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} />}
@@ -304,7 +282,6 @@ interface TimeSeriesViewProps {
dataSource: DataSource;
setWarning?: Dispatch<SetStateAction<Warning | undefined>>;
panelType?: PANEL_TYPES;
stackBarChart?: boolean;
}
TimeSeriesView.defaultProps = {
@@ -313,7 +290,6 @@ TimeSeriesView.defaultProps = {
error: undefined,
setWarning: undefined,
panelType: PANEL_TYPES.TIME_SERIES,
stackBarChart: false,
};
export default TimeSeriesView;

View File

@@ -2,8 +2,8 @@ import { useCallback, useMemo } from 'react';
import { useQueryClient } from 'react-query';
import {
getGetServiceAccountRolesQueryKey,
useCreateServiceAccountRoleDeprecated,
useDeleteServiceAccountRoleDeprecated,
useCreateServiceAccountRole,
useDeleteServiceAccountRole,
useGetServiceAccountRoles,
} from 'api/generated/services/serviceaccount';
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
@@ -46,10 +46,10 @@ export function useServiceAccountRoleManager(
);
// the retry for these mutations is safe due to being idempotent on backend
const { mutateAsync: createRole } = useCreateServiceAccountRoleDeprecated({
const { mutateAsync: createRole } = useCreateServiceAccountRole({
mutation: { retry: retryOn429 },
});
const { mutateAsync: deleteRole } = useDeleteServiceAccountRoleDeprecated({
const { mutateAsync: deleteRole } = useDeleteServiceAccountRole({
mutation: { retry: retryOn429 },
});

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