mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-17 09:20:28 +01:00
Compare commits
17 Commits
dependabot
...
base-path-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d887b4a67e | ||
|
|
c88c7413ac | ||
|
|
622ccf6742 | ||
|
|
21821fde57 | ||
|
|
7208777ca6 | ||
|
|
ad3e0527ce | ||
|
|
555dc5881e | ||
|
|
4761fcb57a | ||
|
|
bda93ee286 | ||
|
|
7edc737076 | ||
|
|
4fa306f94f | ||
|
|
c415b2a1d7 | ||
|
|
d513188511 | ||
|
|
e7edc5da82 | ||
|
|
dae7a43a52 | ||
|
|
9fb373e36b | ||
|
|
b01d4d8a74 |
@@ -190,7 +190,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.119.0
|
||||
image: signoz/signoz:v0.118.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
# - "6060:6060" # pprof port
|
||||
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:v0.119.0
|
||||
image: signoz/signoz:v0.118.0
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
volumes:
|
||||
|
||||
@@ -181,7 +181,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.119.0}
|
||||
image: signoz/signoz:${VERSION:-v0.118.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
# - ../common/clickhouse/storage.xml:/etc/clickhouse-server/config.d/storage.xml
|
||||
signoz:
|
||||
!!merge <<: *db-depend
|
||||
image: signoz/signoz:${VERSION:-v0.119.0}
|
||||
image: signoz/signoz:${VERSION:-v0.118.0}
|
||||
container_name: signoz
|
||||
ports:
|
||||
- "8080:8080" # signoz port
|
||||
|
||||
1926
docs/api/openapi.yml
1926
docs/api/openapi.yml
File diff suppressed because it is too large
Load Diff
@@ -7,12 +7,12 @@ This guide explains how to add new data sources to the SigNoz onboarding flow. T
|
||||
The configuration is located at:
|
||||
|
||||
```
|
||||
frontend/src/container/OnboardingV2Container/onboarding-configs/onboarding-config-with-links.ts
|
||||
frontend/src/container/OnboardingV2Container/onboarding-configs/onboarding-config-with-links.json
|
||||
```
|
||||
|
||||
## Structure Overview
|
||||
## JSON Structure Overview
|
||||
|
||||
The configuration file exports a TypeScript array (`onboardingConfigWithLinks`) containing data source objects. Each object represents a selectable option in the onboarding flow. SVG logos are imported as ES modules at the top of the file.
|
||||
The configuration file is a JSON array containing data source objects. Each object represents a selectable option in the onboarding flow.
|
||||
|
||||
## Data Source Object Keys
|
||||
|
||||
@@ -24,7 +24,7 @@ The configuration file exports a TypeScript array (`onboardingConfigWithLinks`)
|
||||
| `label` | `string` | Display name shown to users (e.g., `"AWS EC2"`) |
|
||||
| `tags` | `string[]` | Array of category tags for grouping (e.g., `["AWS"]`, `["database"]`) |
|
||||
| `module` | `string` | Destination module after onboarding completion |
|
||||
| `imgUrl` | `string` | Imported SVG URL **(SVG required)** (e.g., `import ec2Url from '@/assets/Logos/ec2.svg'`, then use `ec2Url`) |
|
||||
| `imgUrl` | `string` | Path to the logo/icon **(SVG required)** (e.g., `"/Logos/ec2.svg"`) |
|
||||
|
||||
### Optional Keys
|
||||
|
||||
@@ -57,34 +57,36 @@ The `module` key determines where users are redirected after completing onboardi
|
||||
|
||||
The `question` object enables multi-step selection flows:
|
||||
|
||||
```ts
|
||||
question: {
|
||||
desc: 'What would you like to monitor?',
|
||||
type: 'select',
|
||||
helpText: 'Choose the telemetry type you want to collect.',
|
||||
helpLink: '/docs/azure-monitoring/overview/',
|
||||
helpLinkText: 'Read the guide →',
|
||||
options: [
|
||||
{
|
||||
key: 'logging',
|
||||
label: 'Logs',
|
||||
imgUrl: azureVmUrl,
|
||||
link: '/docs/azure-monitoring/app-service/logging/',
|
||||
},
|
||||
{
|
||||
key: 'metrics',
|
||||
label: 'Metrics',
|
||||
imgUrl: azureVmUrl,
|
||||
link: '/docs/azure-monitoring/app-service/metrics/',
|
||||
},
|
||||
{
|
||||
key: 'tracing',
|
||||
label: 'Traces',
|
||||
imgUrl: azureVmUrl,
|
||||
link: '/docs/azure-monitoring/app-service/tracing/',
|
||||
},
|
||||
],
|
||||
},
|
||||
```json
|
||||
{
|
||||
"question": {
|
||||
"desc": "What would you like to monitor?",
|
||||
"type": "select",
|
||||
"helpText": "Choose the telemetry type you want to collect.",
|
||||
"helpLink": "/docs/azure-monitoring/overview/",
|
||||
"helpLinkText": "Read the guide →",
|
||||
"options": [
|
||||
{
|
||||
"key": "logging",
|
||||
"label": "Logs",
|
||||
"imgUrl": "/Logos/azure-vm.svg",
|
||||
"link": "/docs/azure-monitoring/app-service/logging/"
|
||||
},
|
||||
{
|
||||
"key": "metrics",
|
||||
"label": "Metrics",
|
||||
"imgUrl": "/Logos/azure-vm.svg",
|
||||
"link": "/docs/azure-monitoring/app-service/metrics/"
|
||||
},
|
||||
{
|
||||
"key": "tracing",
|
||||
"label": "Traces",
|
||||
"imgUrl": "/Logos/azure-vm.svg",
|
||||
"link": "/docs/azure-monitoring/app-service/tracing/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Question Keys
|
||||
@@ -104,161 +106,152 @@ Options can be simple (direct link) or nested (with another question):
|
||||
|
||||
### Simple Option (Direct Link)
|
||||
|
||||
```ts
|
||||
```json
|
||||
{
|
||||
key: 'aws-ec2-logs',
|
||||
label: 'Logs',
|
||||
imgUrl: ec2Url,
|
||||
link: '/docs/userguide/collect_logs_from_file/',
|
||||
},
|
||||
"key": "aws-ec2-logs",
|
||||
"label": "Logs",
|
||||
"imgUrl": "/Logos/ec2.svg",
|
||||
"link": "/docs/userguide/collect_logs_from_file/"
|
||||
}
|
||||
```
|
||||
|
||||
### Option with Internal Redirect
|
||||
|
||||
```ts
|
||||
```json
|
||||
{
|
||||
key: 'aws-ec2-metrics-one-click',
|
||||
label: 'One Click AWS',
|
||||
imgUrl: ec2Url,
|
||||
link: '/integrations?integration=aws-integration&service=ec2',
|
||||
internalRedirect: true,
|
||||
},
|
||||
"key": "aws-ec2-metrics-one-click",
|
||||
"label": "One Click AWS",
|
||||
"imgUrl": "/Logos/ec2.svg",
|
||||
"link": "/integrations?integration=aws-integration&service=ec2",
|
||||
"internalRedirect": true
|
||||
}
|
||||
```
|
||||
|
||||
> **Important**: Set `internalRedirect: true` only for internal app routes (like `/integrations?...`). Docs links should NOT have this flag.
|
||||
|
||||
### Nested Option (Multi-step Flow)
|
||||
|
||||
```ts
|
||||
```json
|
||||
{
|
||||
key: 'aws-ec2-metrics',
|
||||
label: 'Metrics',
|
||||
imgUrl: ec2Url,
|
||||
question: {
|
||||
desc: 'How would you like to set up monitoring?',
|
||||
helpText: 'Choose your setup method.',
|
||||
options: [...],
|
||||
},
|
||||
},
|
||||
"key": "aws-ec2-metrics",
|
||||
"label": "Metrics",
|
||||
"imgUrl": "/Logos/ec2.svg",
|
||||
"question": {
|
||||
"desc": "How would you like to set up monitoring?",
|
||||
"helpText": "Choose your setup method.",
|
||||
"options": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Simple Data Source (Direct Link)
|
||||
|
||||
```ts
|
||||
import elbUrl from '@/assets/Logos/elb.svg';
|
||||
|
||||
// inside the onboardingConfigWithLinks array:
|
||||
```json
|
||||
{
|
||||
dataSource: 'aws-elb',
|
||||
label: 'AWS ELB',
|
||||
tags: ['AWS'],
|
||||
module: 'logs',
|
||||
relatedSearchKeywords: [
|
||||
'aws',
|
||||
'aws elb',
|
||||
'elb logs',
|
||||
'elastic load balancer',
|
||||
"dataSource": "aws-elb",
|
||||
"label": "AWS ELB",
|
||||
"tags": ["AWS"],
|
||||
"module": "logs",
|
||||
"relatedSearchKeywords": [
|
||||
"aws",
|
||||
"aws elb",
|
||||
"elb logs",
|
||||
"elastic load balancer"
|
||||
],
|
||||
imgUrl: elbUrl,
|
||||
link: '/docs/aws-monitoring/elb/',
|
||||
},
|
||||
"imgUrl": "/Logos/elb.svg",
|
||||
"link": "/docs/aws-monitoring/elb/"
|
||||
}
|
||||
```
|
||||
|
||||
### Data Source with Single Question Level
|
||||
|
||||
```ts
|
||||
import azureVmUrl from '@/assets/Logos/azure-vm.svg';
|
||||
|
||||
// inside the onboardingConfigWithLinks array:
|
||||
```json
|
||||
{
|
||||
dataSource: 'app-service',
|
||||
label: 'App Service',
|
||||
imgUrl: azureVmUrl,
|
||||
tags: ['Azure'],
|
||||
module: 'apm',
|
||||
relatedSearchKeywords: ['azure', 'app service'],
|
||||
question: {
|
||||
desc: 'What telemetry data do you want to visualise?',
|
||||
type: 'select',
|
||||
options: [
|
||||
"dataSource": "app-service",
|
||||
"label": "App Service",
|
||||
"imgUrl": "/Logos/azure-vm.svg",
|
||||
"tags": ["Azure"],
|
||||
"module": "apm",
|
||||
"relatedSearchKeywords": ["azure", "app service"],
|
||||
"question": {
|
||||
"desc": "What telemetry data do you want to visualise?",
|
||||
"type": "select",
|
||||
"options": [
|
||||
{
|
||||
key: 'logging',
|
||||
label: 'Logs',
|
||||
imgUrl: azureVmUrl,
|
||||
link: '/docs/azure-monitoring/app-service/logging/',
|
||||
"key": "logging",
|
||||
"label": "Logs",
|
||||
"imgUrl": "/Logos/azure-vm.svg",
|
||||
"link": "/docs/azure-monitoring/app-service/logging/"
|
||||
},
|
||||
{
|
||||
key: 'metrics',
|
||||
label: 'Metrics',
|
||||
imgUrl: azureVmUrl,
|
||||
link: '/docs/azure-monitoring/app-service/metrics/',
|
||||
"key": "metrics",
|
||||
"label": "Metrics",
|
||||
"imgUrl": "/Logos/azure-vm.svg",
|
||||
"link": "/docs/azure-monitoring/app-service/metrics/"
|
||||
},
|
||||
{
|
||||
key: 'tracing',
|
||||
label: 'Traces',
|
||||
imgUrl: azureVmUrl,
|
||||
link: '/docs/azure-monitoring/app-service/tracing/',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"key": "tracing",
|
||||
"label": "Traces",
|
||||
"imgUrl": "/Logos/azure-vm.svg",
|
||||
"link": "/docs/azure-monitoring/app-service/tracing/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Data Source with Nested Questions (2-3 Levels)
|
||||
|
||||
```ts
|
||||
import ec2Url from '@/assets/Logos/ec2.svg';
|
||||
|
||||
// inside the onboardingConfigWithLinks array:
|
||||
```json
|
||||
{
|
||||
dataSource: 'aws-ec2',
|
||||
label: 'AWS EC2',
|
||||
tags: ['AWS'],
|
||||
module: 'logs',
|
||||
relatedSearchKeywords: ['aws', 'aws ec2', 'ec2 logs', 'ec2 metrics'],
|
||||
imgUrl: ec2Url,
|
||||
question: {
|
||||
desc: 'What would you like to monitor for AWS EC2?',
|
||||
type: 'select',
|
||||
helpText: 'Choose the type of telemetry data you want to collect.',
|
||||
options: [
|
||||
"dataSource": "aws-ec2",
|
||||
"label": "AWS EC2",
|
||||
"tags": ["AWS"],
|
||||
"module": "logs",
|
||||
"relatedSearchKeywords": ["aws", "aws ec2", "ec2 logs", "ec2 metrics"],
|
||||
"imgUrl": "/Logos/ec2.svg",
|
||||
"question": {
|
||||
"desc": "What would you like to monitor for AWS EC2?",
|
||||
"type": "select",
|
||||
"helpText": "Choose the type of telemetry data you want to collect.",
|
||||
"options": [
|
||||
{
|
||||
key: 'aws-ec2-logs',
|
||||
label: 'Logs',
|
||||
imgUrl: ec2Url,
|
||||
link: '/docs/userguide/collect_logs_from_file/',
|
||||
"key": "aws-ec2-logs",
|
||||
"label": "Logs",
|
||||
"imgUrl": "/Logos/ec2.svg",
|
||||
"link": "/docs/userguide/collect_logs_from_file/"
|
||||
},
|
||||
{
|
||||
key: 'aws-ec2-metrics',
|
||||
label: 'Metrics',
|
||||
imgUrl: ec2Url,
|
||||
question: {
|
||||
desc: 'How would you like to set up EC2 Metrics monitoring?',
|
||||
helpText: 'One Click uses AWS CloudWatch integration. Manual setup uses OpenTelemetry.',
|
||||
helpLink: '/docs/aws-monitoring/one-click-vs-manual/',
|
||||
helpLinkText: 'Read the comparison guide →',
|
||||
options: [
|
||||
"key": "aws-ec2-metrics",
|
||||
"label": "Metrics",
|
||||
"imgUrl": "/Logos/ec2.svg",
|
||||
"question": {
|
||||
"desc": "How would you like to set up EC2 Metrics monitoring?",
|
||||
"helpText": "One Click uses AWS CloudWatch integration. Manual setup uses OpenTelemetry.",
|
||||
"helpLink": "/docs/aws-monitoring/one-click-vs-manual/",
|
||||
"helpLinkText": "Read the comparison guide →",
|
||||
"options": [
|
||||
{
|
||||
key: 'aws-ec2-metrics-one-click',
|
||||
label: 'One Click AWS',
|
||||
imgUrl: ec2Url,
|
||||
link: '/integrations?integration=aws-integration&service=ec2',
|
||||
internalRedirect: true,
|
||||
"key": "aws-ec2-metrics-one-click",
|
||||
"label": "One Click AWS",
|
||||
"imgUrl": "/Logos/ec2.svg",
|
||||
"link": "/integrations?integration=aws-integration&service=ec2",
|
||||
"internalRedirect": true
|
||||
},
|
||||
{
|
||||
key: 'aws-ec2-metrics-manual',
|
||||
label: 'Manual Setup',
|
||||
imgUrl: ec2Url,
|
||||
link: '/docs/tutorial/opentelemetry-binary-usage-in-virtual-machine/',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
"key": "aws-ec2-metrics-manual",
|
||||
"label": "Manual Setup",
|
||||
"imgUrl": "/Logos/ec2.svg",
|
||||
"link": "/docs/tutorial/opentelemetry-binary-usage-in-virtual-machine/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
@@ -277,16 +270,11 @@ import ec2Url from '@/assets/Logos/ec2.svg';
|
||||
|
||||
### 3. Logos
|
||||
|
||||
- Place logo files in `src/assets/Logos/`
|
||||
- Place logo files in `public/Logos/`
|
||||
- Use SVG format
|
||||
- Import the SVG at the top of the file and reference the imported variable:
|
||||
```ts
|
||||
import myServiceUrl from '@/assets/Logos/my-service.svg';
|
||||
// then in the config object:
|
||||
imgUrl: myServiceUrl,
|
||||
```
|
||||
- Reference as `"/Logos/your-logo.svg"`
|
||||
- **Fetching Icons**: New icons can be easily fetched from [OpenBrand](https://openbrand.sh/). Use the pattern `https://openbrand.sh/?url=<TARGET_URL>`, where `<TARGET_URL>` is the URL-encoded link to the service's website. For example, to get Render's logo, use [https://openbrand.sh/?url=https%3A%2F%2Frender.com](https://openbrand.sh/?url=https%3A%2F%2Frender.com).
|
||||
- **Optimize new SVGs**: Run any newly downloaded SVGs through an optimizer like [SVGOMG (svgo)](https://svgomg.net/) or use `npx svgo src/assets/Logos/your-logo.svg` to minimise their size before committing.
|
||||
- **Optimize new SVGs**: Run any newly downloaded SVGs through an optimizer like [SVGOMG (svgo)](https://svgomg.net/) or use `npx svgo public/Logos/your-logo.svg` to minimise their size before committing.
|
||||
|
||||
### 4. Links
|
||||
|
||||
@@ -302,8 +290,8 @@ import ec2Url from '@/assets/Logos/ec2.svg';
|
||||
|
||||
## Adding a New Data Source
|
||||
|
||||
1. Add the logo SVG to `src/assets/Logos/` and add a top-level import in the config file (e.g., `import myServiceUrl from '@/assets/Logos/my-service.svg'`)
|
||||
2. Add your data source object to the `onboardingConfigWithLinks` array, referencing the imported variable for `imgUrl`
|
||||
1. Add your data source object to the JSON array
|
||||
2. Ensure the logo exists in `public/Logos/`
|
||||
3. Test the flow locally with `yarn dev`
|
||||
4. Validation:
|
||||
- Navigate to the [onboarding page](http://localhost:3301/get-started-with-signoz-cloud) on your local machine
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||
"github.com/SigNoz/signoz/ee/query-service/usage"
|
||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
@@ -48,6 +49,7 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz, config signoz.
|
||||
CloudIntegrationsController: opts.CloudIntegrationsController,
|
||||
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
||||
FluxInterval: opts.FluxInterval,
|
||||
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
||||
LicensingAPI: httplicensing.NewLicensingAPI(signoz.Licensing),
|
||||
Signoz: signoz,
|
||||
QueryParserAPI: queryparser.NewAPI(signoz.Instrumentation.ToProviderSettings(), signoz.QueryParser),
|
||||
|
||||
@@ -213,6 +213,23 @@ module.exports = {
|
||||
message:
|
||||
'Avoid calling .getState() directly. Export a standalone action from the store instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.name='window'][callee.property.name='open']",
|
||||
message:
|
||||
'Do not call window.open() directly. ' +
|
||||
"Use openInNewTab() from 'utils/navigation' for internal SigNoz paths. " +
|
||||
"For intentional external URLs, use openExternalLink() from 'utils/navigation'. " +
|
||||
'For unavoidable direct calls, add // eslint-disable-next-line with a reason.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"AssignmentExpression[left.object.name='window'][left.property.name='href']",
|
||||
message:
|
||||
'Do not assign window.location.href for internal navigation. ' +
|
||||
"Use history.push() or history.replace() from 'lib/history'. " +
|
||||
'For external redirects (SSO, logout URLs), add // eslint-disable-next-line with a reason.',
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
@@ -265,5 +282,14 @@ module.exports = {
|
||||
'no-restricted-syntax': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// navigation.ts and useSafeNavigate.ts are the canonical implementations that call
|
||||
// window.open after computing a base-path-aware href. They are the only places
|
||||
// allowed to call window.open directly.
|
||||
files: ['src/utils/navigation.ts', 'src/hooks/useSafeNavigate.ts'],
|
||||
rules: {
|
||||
'no-restricted-syntax': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -6,3 +6,6 @@ VITE_APPCUES_APP_ID="appcess-app-id"
|
||||
VITE_PYLON_IDENTITY_SECRET="pylon-identity-secret"
|
||||
|
||||
CI="1"
|
||||
|
||||
# Uncomment to test sub-path deployment locally (e.g. app served at /signoz/).
|
||||
# VITE_BASE_PATH="/signoz/"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="<%- BASE_PATH %>" />
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
http-equiv="Cache-Control"
|
||||
@@ -59,7 +60,7 @@
|
||||
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
|
||||
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
|
||||
<link data-react-helmet="true" rel="shortcut icon" href="./favicon.ico" />
|
||||
</head>
|
||||
<body data-theme="default">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
@@ -113,7 +114,7 @@
|
||||
})(document, 'script');
|
||||
}
|
||||
</script>
|
||||
<link rel="stylesheet" href="/css/uPlot.min.css" />
|
||||
<link rel="stylesheet" href="./css/uPlot.min.css" />
|
||||
<script type="module" src="./src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -20,6 +20,7 @@ const config: Config.InitialOptions = {
|
||||
'^@signozhq/resizable$': '<rootDir>/__mocks__/resizableMock.tsx',
|
||||
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^hooks/useSafeNavigate\\.impl$': '<rootDir>/src/hooks/useSafeNavigate.ts',
|
||||
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
'^src/constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import type { ErrorType } from '../../../generatedAPIInstance';
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type { GetAlerts200, RenderErrorResponseDTO } from '../sigNoz.schemas';
|
||||
|
||||
/**
|
||||
* This endpoint returns alerts for the organization
|
||||
* @summary Get alerts
|
||||
*/
|
||||
export const getAlerts = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<GetAlerts200>({
|
||||
url: `/api/v1/alerts`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetAlertsQueryKey = () => {
|
||||
return [`/api/v1/alerts`] as const;
|
||||
};
|
||||
|
||||
export const getGetAlertsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getAlerts>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof getAlerts>>, TError, TData>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetAlertsQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getAlerts>>> = ({
|
||||
signal,
|
||||
}) => getAlerts(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getAlerts>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetAlertsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getAlerts>>
|
||||
>;
|
||||
export type GetAlertsQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get alerts
|
||||
*/
|
||||
|
||||
export function useGetAlerts<
|
||||
TData = Awaited<ReturnType<typeof getAlerts>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<Awaited<ReturnType<typeof getAlerts>>, TError, TData>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetAlertsQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get alerts
|
||||
*/
|
||||
export const invalidateGetAlerts = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetAlertsQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
@@ -1,646 +0,0 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type {
|
||||
ConfigReceiverDTO,
|
||||
CreateChannel201,
|
||||
DeleteChannelByIDPathParameters,
|
||||
GetChannelByID200,
|
||||
GetChannelByIDPathParameters,
|
||||
ListChannels200,
|
||||
RenderErrorResponseDTO,
|
||||
UpdateChannelByIDPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
/**
|
||||
* This endpoint lists all notification channels for the organization
|
||||
* @summary List notification channels
|
||||
*/
|
||||
export const listChannels = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<ListChannels200>({
|
||||
url: `/api/v1/channels`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListChannelsQueryKey = () => {
|
||||
return [`/api/v1/channels`] as const;
|
||||
};
|
||||
|
||||
export const getListChannelsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listChannels>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listChannels>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListChannelsQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listChannels>>> = ({
|
||||
signal,
|
||||
}) => listChannels(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listChannels>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListChannelsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listChannels>>
|
||||
>;
|
||||
export type ListChannelsQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List notification channels
|
||||
*/
|
||||
|
||||
export function useListChannels<
|
||||
TData = Awaited<ReturnType<typeof listChannels>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listChannels>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListChannelsQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List notification channels
|
||||
*/
|
||||
export const invalidateListChannels = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListChannelsQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates a notification channel
|
||||
* @summary Create notification channel
|
||||
*/
|
||||
export const createChannel = (
|
||||
configReceiverDTO: BodyType<ConfigReceiverDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateChannel201>({
|
||||
url: `/api/v1/channels`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: configReceiverDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateChannelMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createChannel'];
|
||||
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 createChannel>>,
|
||||
{ data: BodyType<ConfigReceiverDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createChannel(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateChannelMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createChannel>>
|
||||
>;
|
||||
export type CreateChannelMutationBody = BodyType<ConfigReceiverDTO>;
|
||||
export type CreateChannelMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create notification channel
|
||||
*/
|
||||
export const useCreateChannel = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateChannelMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint deletes a notification channel by ID
|
||||
* @summary Delete notification channel
|
||||
*/
|
||||
export const deleteChannelByID = ({ id }: DeleteChannelByIDPathParameters) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/channels/${id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteChannelByIDMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteChannelByID>>,
|
||||
TError,
|
||||
{ pathParams: DeleteChannelByIDPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteChannelByID>>,
|
||||
TError,
|
||||
{ pathParams: DeleteChannelByIDPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteChannelByID'];
|
||||
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 deleteChannelByID>>,
|
||||
{ pathParams: DeleteChannelByIDPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteChannelByID(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteChannelByIDMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteChannelByID>>
|
||||
>;
|
||||
|
||||
export type DeleteChannelByIDMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete notification channel
|
||||
*/
|
||||
export const useDeleteChannelByID = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteChannelByID>>,
|
||||
TError,
|
||||
{ pathParams: DeleteChannelByIDPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteChannelByID>>,
|
||||
TError,
|
||||
{ pathParams: DeleteChannelByIDPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getDeleteChannelByIDMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint returns a notification channel by ID
|
||||
* @summary Get notification channel by ID
|
||||
*/
|
||||
export const getChannelByID = (
|
||||
{ id }: GetChannelByIDPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetChannelByID200>({
|
||||
url: `/api/v1/channels/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetChannelByIDQueryKey = ({
|
||||
id,
|
||||
}: GetChannelByIDPathParameters) => {
|
||||
return [`/api/v1/channels/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetChannelByIDQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getChannelByID>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: GetChannelByIDPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getChannelByID>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetChannelByIDQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getChannelByID>>> = ({
|
||||
signal,
|
||||
}) => getChannelByID({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getChannelByID>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetChannelByIDQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getChannelByID>>
|
||||
>;
|
||||
export type GetChannelByIDQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get notification channel by ID
|
||||
*/
|
||||
|
||||
export function useGetChannelByID<
|
||||
TData = Awaited<ReturnType<typeof getChannelByID>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: GetChannelByIDPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getChannelByID>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetChannelByIDQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get notification channel by ID
|
||||
*/
|
||||
export const invalidateGetChannelByID = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetChannelByIDPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetChannelByIDQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint updates a notification channel by ID
|
||||
* @summary Update notification channel
|
||||
*/
|
||||
export const updateChannelByID = (
|
||||
{ id }: UpdateChannelByIDPathParameters,
|
||||
configReceiverDTO: BodyType<ConfigReceiverDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/channels/${id}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: configReceiverDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateChannelByIDMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateChannelByID>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateChannelByIDPathParameters;
|
||||
data: BodyType<ConfigReceiverDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateChannelByID>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateChannelByIDPathParameters;
|
||||
data: BodyType<ConfigReceiverDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateChannelByID'];
|
||||
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 updateChannelByID>>,
|
||||
{
|
||||
pathParams: UpdateChannelByIDPathParameters;
|
||||
data: BodyType<ConfigReceiverDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateChannelByID(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateChannelByIDMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateChannelByID>>
|
||||
>;
|
||||
export type UpdateChannelByIDMutationBody = BodyType<ConfigReceiverDTO>;
|
||||
export type UpdateChannelByIDMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update notification channel
|
||||
*/
|
||||
export const useUpdateChannelByID = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateChannelByID>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateChannelByIDPathParameters;
|
||||
data: BodyType<ConfigReceiverDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateChannelByID>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateChannelByIDPathParameters;
|
||||
data: BodyType<ConfigReceiverDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateChannelByIDMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint tests a notification channel by sending a test notification
|
||||
* @summary Test notification channel
|
||||
*/
|
||||
export const testChannel = (
|
||||
configReceiverDTO: BodyType<ConfigReceiverDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/channels/test`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: configReceiverDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getTestChannelMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof testChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof testChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['testChannel'];
|
||||
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 testChannel>>,
|
||||
{ data: BodyType<ConfigReceiverDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return testChannel(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type TestChannelMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof testChannel>>
|
||||
>;
|
||||
export type TestChannelMutationBody = BodyType<ConfigReceiverDTO>;
|
||||
export type TestChannelMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Test notification channel
|
||||
*/
|
||||
export const useTestChannel = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof testChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof testChannel>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getTestChannelMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Deprecated: use /api/v1/channels/test instead
|
||||
* @deprecated
|
||||
* @summary Test notification channel (deprecated)
|
||||
*/
|
||||
export const testChannelDeprecated = (
|
||||
configReceiverDTO: BodyType<ConfigReceiverDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/testChannel`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: configReceiverDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getTestChannelDeprecatedMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof testChannelDeprecated>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof testChannelDeprecated>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['testChannelDeprecated'];
|
||||
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 testChannelDeprecated>>,
|
||||
{ data: BodyType<ConfigReceiverDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return testChannelDeprecated(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type TestChannelDeprecatedMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof testChannelDeprecated>>
|
||||
>;
|
||||
export type TestChannelDeprecatedMutationBody = BodyType<ConfigReceiverDTO>;
|
||||
export type TestChannelDeprecatedMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Test notification channel (deprecated)
|
||||
*/
|
||||
export const useTestChannelDeprecated = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof testChannelDeprecated>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof testChannelDeprecated>>,
|
||||
TError,
|
||||
{ data: BodyType<ConfigReceiverDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getTestChannelDeprecatedMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
@@ -1,482 +0,0 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type {
|
||||
AlertmanagertypesPostableRoutePolicyDTO,
|
||||
CreateRoutePolicy201,
|
||||
DeleteRoutePolicyByIDPathParameters,
|
||||
GetAllRoutePolicies200,
|
||||
GetRoutePolicyByID200,
|
||||
GetRoutePolicyByIDPathParameters,
|
||||
RenderErrorResponseDTO,
|
||||
UpdateRoutePolicy200,
|
||||
UpdateRoutePolicyPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
/**
|
||||
* This endpoint lists all route policies for the organization
|
||||
* @summary List route policies
|
||||
*/
|
||||
export const getAllRoutePolicies = (signal?: AbortSignal) => {
|
||||
return GeneratedAPIInstance<GetAllRoutePolicies200>({
|
||||
url: `/api/v1/route_policies`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetAllRoutePoliciesQueryKey = () => {
|
||||
return [`/api/v1/route_policies`] as const;
|
||||
};
|
||||
|
||||
export const getGetAllRoutePoliciesQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getAllRoutePolicies>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getAllRoutePolicies>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetAllRoutePoliciesQueryKey();
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getAllRoutePolicies>>
|
||||
> = ({ signal }) => getAllRoutePolicies(signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getAllRoutePolicies>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetAllRoutePoliciesQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getAllRoutePolicies>>
|
||||
>;
|
||||
export type GetAllRoutePoliciesQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List route policies
|
||||
*/
|
||||
|
||||
export function useGetAllRoutePolicies<
|
||||
TData = Awaited<ReturnType<typeof getAllRoutePolicies>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getAllRoutePolicies>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetAllRoutePoliciesQueryOptions(options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List route policies
|
||||
*/
|
||||
export const invalidateGetAllRoutePolicies = async (
|
||||
queryClient: QueryClient,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetAllRoutePoliciesQueryKey() },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates a route policy
|
||||
* @summary Create route policy
|
||||
*/
|
||||
export const createRoutePolicy = (
|
||||
alertmanagertypesPostableRoutePolicyDTO: BodyType<AlertmanagertypesPostableRoutePolicyDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateRoutePolicy201>({
|
||||
url: `/api/v1/route_policies`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: alertmanagertypesPostableRoutePolicyDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateRoutePolicyMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRoutePolicy>>,
|
||||
TError,
|
||||
{ data: BodyType<AlertmanagertypesPostableRoutePolicyDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRoutePolicy>>,
|
||||
TError,
|
||||
{ data: BodyType<AlertmanagertypesPostableRoutePolicyDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createRoutePolicy'];
|
||||
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 createRoutePolicy>>,
|
||||
{ data: BodyType<AlertmanagertypesPostableRoutePolicyDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createRoutePolicy(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateRoutePolicyMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createRoutePolicy>>
|
||||
>;
|
||||
export type CreateRoutePolicyMutationBody = BodyType<AlertmanagertypesPostableRoutePolicyDTO>;
|
||||
export type CreateRoutePolicyMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create route policy
|
||||
*/
|
||||
export const useCreateRoutePolicy = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createRoutePolicy>>,
|
||||
TError,
|
||||
{ data: BodyType<AlertmanagertypesPostableRoutePolicyDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createRoutePolicy>>,
|
||||
TError,
|
||||
{ data: BodyType<AlertmanagertypesPostableRoutePolicyDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateRoutePolicyMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint deletes a route policy by ID
|
||||
* @summary Delete route policy
|
||||
*/
|
||||
export const deleteRoutePolicyByID = ({
|
||||
id,
|
||||
}: DeleteRoutePolicyByIDPathParameters) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/route_policies/${id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteRoutePolicyByIDMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteRoutePolicyByID>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRoutePolicyByIDPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteRoutePolicyByID>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRoutePolicyByIDPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteRoutePolicyByID'];
|
||||
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 deleteRoutePolicyByID>>,
|
||||
{ pathParams: DeleteRoutePolicyByIDPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteRoutePolicyByID(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteRoutePolicyByIDMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteRoutePolicyByID>>
|
||||
>;
|
||||
|
||||
export type DeleteRoutePolicyByIDMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete route policy
|
||||
*/
|
||||
export const useDeleteRoutePolicyByID = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteRoutePolicyByID>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRoutePolicyByIDPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteRoutePolicyByID>>,
|
||||
TError,
|
||||
{ pathParams: DeleteRoutePolicyByIDPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getDeleteRoutePolicyByIDMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint returns a route policy by ID
|
||||
* @summary Get route policy by ID
|
||||
*/
|
||||
export const getRoutePolicyByID = (
|
||||
{ id }: GetRoutePolicyByIDPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetRoutePolicyByID200>({
|
||||
url: `/api/v1/route_policies/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetRoutePolicyByIDQueryKey = ({
|
||||
id,
|
||||
}: GetRoutePolicyByIDPathParameters) => {
|
||||
return [`/api/v1/route_policies/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetRoutePolicyByIDQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getRoutePolicyByID>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: GetRoutePolicyByIDPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getRoutePolicyByID>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetRoutePolicyByIDQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getRoutePolicyByID>>
|
||||
> = ({ signal }) => getRoutePolicyByID({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getRoutePolicyByID>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetRoutePolicyByIDQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getRoutePolicyByID>>
|
||||
>;
|
||||
export type GetRoutePolicyByIDQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get route policy by ID
|
||||
*/
|
||||
|
||||
export function useGetRoutePolicyByID<
|
||||
TData = Awaited<ReturnType<typeof getRoutePolicyByID>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: GetRoutePolicyByIDPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getRoutePolicyByID>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetRoutePolicyByIDQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get route policy by ID
|
||||
*/
|
||||
export const invalidateGetRoutePolicyByID = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetRoutePolicyByIDPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetRoutePolicyByIDQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint updates a route policy by ID
|
||||
* @summary Update route policy
|
||||
*/
|
||||
export const updateRoutePolicy = (
|
||||
{ id }: UpdateRoutePolicyPathParameters,
|
||||
alertmanagertypesPostableRoutePolicyDTO: BodyType<AlertmanagertypesPostableRoutePolicyDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<UpdateRoutePolicy200>({
|
||||
url: `/api/v1/route_policies/${id}`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: alertmanagertypesPostableRoutePolicyDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateRoutePolicyMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateRoutePolicy>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateRoutePolicyPathParameters;
|
||||
data: BodyType<AlertmanagertypesPostableRoutePolicyDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateRoutePolicy>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateRoutePolicyPathParameters;
|
||||
data: BodyType<AlertmanagertypesPostableRoutePolicyDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateRoutePolicy'];
|
||||
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 updateRoutePolicy>>,
|
||||
{
|
||||
pathParams: UpdateRoutePolicyPathParameters;
|
||||
data: BodyType<AlertmanagertypesPostableRoutePolicyDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateRoutePolicy(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateRoutePolicyMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateRoutePolicy>>
|
||||
>;
|
||||
export type UpdateRoutePolicyMutationBody = BodyType<AlertmanagertypesPostableRoutePolicyDTO>;
|
||||
export type UpdateRoutePolicyMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update route policy
|
||||
*/
|
||||
export const useUpdateRoutePolicy = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateRoutePolicy>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateRoutePolicyPathParameters;
|
||||
data: BodyType<AlertmanagertypesPostableRoutePolicyDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateRoutePolicy>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateRoutePolicyPathParameters;
|
||||
data: BodyType<AlertmanagertypesPostableRoutePolicyDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateRoutePolicyMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -20,15 +20,12 @@ import { useMutation, useQuery } from 'react-query';
|
||||
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type {
|
||||
ChangePasswordPathParameters,
|
||||
CreateInvite201,
|
||||
CreateResetPasswordToken201,
|
||||
CreateResetPasswordTokenPathParameters,
|
||||
DeleteUserPathParameters,
|
||||
GetMyUser200,
|
||||
GetMyUserDeprecated200,
|
||||
GetResetPasswordToken200,
|
||||
GetResetPasswordTokenDeprecated200,
|
||||
GetResetPasswordTokenDeprecatedPathParameters,
|
||||
GetResetPasswordTokenPathParameters,
|
||||
GetRolesByUserID200,
|
||||
GetRolesByUserIDPathParameters,
|
||||
@@ -57,35 +54,133 @@ import type {
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
/**
|
||||
* This endpoint returns the reset password token by id
|
||||
* @deprecated
|
||||
* @summary Get reset password token
|
||||
* This endpoint changes the password by id
|
||||
* @summary Change password
|
||||
*/
|
||||
export const getResetPasswordTokenDeprecated = (
|
||||
{ id }: GetResetPasswordTokenDeprecatedPathParameters,
|
||||
export const changePassword = (
|
||||
{ id }: ChangePasswordPathParameters,
|
||||
typesChangePasswordRequestDTO: BodyType<TypesChangePasswordRequestDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetResetPasswordTokenDeprecated200>({
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/changePassword/${id}`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesChangePasswordRequestDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getChangePasswordMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof changePassword>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof changePassword>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['changePassword'];
|
||||
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 changePassword>>,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return changePassword(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type ChangePasswordMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof changePassword>>
|
||||
>;
|
||||
export type ChangePasswordMutationBody = BodyType<TypesChangePasswordRequestDTO>;
|
||||
export type ChangePasswordMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Change password
|
||||
*/
|
||||
export const useChangePassword = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof changePassword>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof changePassword>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: ChangePasswordPathParameters;
|
||||
data: BodyType<TypesChangePasswordRequestDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getChangePasswordMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the reset password token by id
|
||||
* @summary Get reset password token
|
||||
*/
|
||||
export const getResetPasswordToken = (
|
||||
{ id }: GetResetPasswordTokenPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetResetPasswordToken200>({
|
||||
url: `/api/v1/getResetPasswordToken/${id}`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetResetPasswordTokenDeprecatedQueryKey = ({
|
||||
export const getGetResetPasswordTokenQueryKey = ({
|
||||
id,
|
||||
}: GetResetPasswordTokenDeprecatedPathParameters) => {
|
||||
}: GetResetPasswordTokenPathParameters) => {
|
||||
return [`/api/v1/getResetPasswordToken/${id}`] as const;
|
||||
};
|
||||
|
||||
export const getGetResetPasswordTokenDeprecatedQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
|
||||
export const getGetResetPasswordTokenQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: GetResetPasswordTokenDeprecatedPathParameters,
|
||||
{ id }: GetResetPasswordTokenPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
@@ -94,11 +189,11 @@ export const getGetResetPasswordTokenDeprecatedQueryOptions = <
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetResetPasswordTokenDeprecatedQueryKey({ id });
|
||||
queryOptions?.queryKey ?? getGetResetPasswordTokenQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>
|
||||
> = ({ signal }) => getResetPasswordTokenDeprecated({ id }, signal);
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>
|
||||
> = ({ signal }) => getResetPasswordToken({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
@@ -106,39 +201,35 @@ export const getGetResetPasswordTokenDeprecatedQueryOptions = <
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetResetPasswordTokenDeprecatedQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>
|
||||
export type GetResetPasswordTokenQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>
|
||||
>;
|
||||
export type GetResetPasswordTokenDeprecatedQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
export type GetResetPasswordTokenQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Get reset password token
|
||||
*/
|
||||
|
||||
export function useGetResetPasswordTokenDeprecated<
|
||||
TData = Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
|
||||
export function useGetResetPasswordToken<
|
||||
TData = Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: GetResetPasswordTokenDeprecatedPathParameters,
|
||||
{ id }: GetResetPasswordTokenPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getResetPasswordTokenDeprecated>>,
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetResetPasswordTokenDeprecatedQueryOptions(
|
||||
{ id },
|
||||
options,
|
||||
);
|
||||
const queryOptions = getGetResetPasswordTokenQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
@@ -150,16 +241,15 @@ export function useGetResetPasswordTokenDeprecated<
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* @summary Get reset password token
|
||||
*/
|
||||
export const invalidateGetResetPasswordTokenDeprecated = async (
|
||||
export const invalidateGetResetPasswordToken = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetResetPasswordTokenDeprecatedPathParameters,
|
||||
{ id }: GetResetPasswordTokenPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetResetPasswordTokenDeprecatedQueryKey({ id }) },
|
||||
{ queryKey: getGetResetPasswordTokenQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
@@ -1317,189 +1407,6 @@ export const useUpdateUser = <
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the existing reset password token for a user.
|
||||
* @summary Get reset password token for a user
|
||||
*/
|
||||
export const getResetPasswordToken = (
|
||||
{ id }: GetResetPasswordTokenPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<GetResetPasswordToken200>({
|
||||
url: `/api/v2/users/${id}/reset_password_tokens`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getGetResetPasswordTokenQueryKey = ({
|
||||
id,
|
||||
}: GetResetPasswordTokenPathParameters) => {
|
||||
return [`/api/v2/users/${id}/reset_password_tokens`] as const;
|
||||
};
|
||||
|
||||
export const getGetResetPasswordTokenQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: GetResetPasswordTokenPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getGetResetPasswordTokenQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>
|
||||
> = ({ signal }) => getResetPasswordToken({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type GetResetPasswordTokenQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>
|
||||
>;
|
||||
export type GetResetPasswordTokenQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Get reset password token for a user
|
||||
*/
|
||||
|
||||
export function useGetResetPasswordToken<
|
||||
TData = Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: GetResetPasswordTokenPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof getResetPasswordToken>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getGetResetPasswordTokenQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get reset password token for a user
|
||||
*/
|
||||
export const invalidateGetResetPasswordToken = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: GetResetPasswordTokenPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getGetResetPasswordTokenQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* This endpoint creates or regenerates a reset password token for a user. If a valid token exists, it is returned. If expired, a new one is created.
|
||||
* @summary Create or regenerate reset password token for a user
|
||||
*/
|
||||
export const createResetPasswordToken = ({
|
||||
id,
|
||||
}: CreateResetPasswordTokenPathParameters) => {
|
||||
return GeneratedAPIInstance<CreateResetPasswordToken201>({
|
||||
url: `/api/v2/users/${id}/reset_password_tokens`,
|
||||
method: 'PUT',
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateResetPasswordTokenMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createResetPasswordToken>>,
|
||||
TError,
|
||||
{ pathParams: CreateResetPasswordTokenPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createResetPasswordToken>>,
|
||||
TError,
|
||||
{ pathParams: CreateResetPasswordTokenPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createResetPasswordToken'];
|
||||
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 createResetPasswordToken>>,
|
||||
{ pathParams: CreateResetPasswordTokenPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return createResetPasswordToken(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateResetPasswordTokenMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createResetPasswordToken>>
|
||||
>;
|
||||
|
||||
export type CreateResetPasswordTokenMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create or regenerate reset password token for a user
|
||||
*/
|
||||
export const useCreateResetPasswordToken = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createResetPasswordToken>>,
|
||||
TError,
|
||||
{ pathParams: CreateResetPasswordTokenPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createResetPasswordToken>>,
|
||||
TError,
|
||||
{ pathParams: CreateResetPasswordTokenPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateResetPasswordTokenMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint returns the user roles by user id
|
||||
* @summary Get user roles
|
||||
@@ -1943,84 +1850,3 @@ export const useUpdateMyUserV2 = <
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* This endpoint updates the password of the user I belong to
|
||||
* @summary Updates my password
|
||||
*/
|
||||
export const updateMyPassword = (
|
||||
typesChangePasswordRequestDTO: BodyType<TypesChangePasswordRequestDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v2/users/me/factor_password`,
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: typesChangePasswordRequestDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateMyPasswordMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMyPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesChangePasswordRequestDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMyPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesChangePasswordRequestDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateMyPassword'];
|
||||
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 updateMyPassword>>,
|
||||
{ data: BodyType<TypesChangePasswordRequestDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return updateMyPassword(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateMyPasswordMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateMyPassword>>
|
||||
>;
|
||||
export type UpdateMyPasswordMutationBody = BodyType<TypesChangePasswordRequestDTO>;
|
||||
export type UpdateMyPasswordMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Updates my password
|
||||
*/
|
||||
export const useUpdateMyPassword = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMyPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesChangePasswordRequestDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateMyPassword>>,
|
||||
TError,
|
||||
{ data: BodyType<TypesChangePasswordRequestDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateMyPasswordMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
|
||||
27
frontend/src/api/v1/factor_password/changeMyPassword.ts
Normal file
27
frontend/src/api/v1/factor_password/changeMyPassword.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/user/changeMyPassword';
|
||||
|
||||
const changeMyPassword = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(
|
||||
`/changePassword/${props.userId}`,
|
||||
{
|
||||
...props,
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default changeMyPassword;
|
||||
28
frontend/src/api/v1/factor_password/getResetPasswordToken.ts
Normal file
28
frontend/src/api/v1/factor_password/getResetPasswordToken.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||
import {
|
||||
GetResetPasswordToken,
|
||||
PayloadProps,
|
||||
Props,
|
||||
} from 'types/api/user/getResetPasswordToken';
|
||||
|
||||
const getResetPasswordToken = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponseV2<GetResetPasswordToken>> => {
|
||||
try {
|
||||
const response = await axios.get<PayloadProps>(
|
||||
`/getResetPasswordToken/${props.userId}`,
|
||||
);
|
||||
|
||||
return {
|
||||
httpStatusCode: response.status,
|
||||
data: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
}
|
||||
};
|
||||
|
||||
export default getResetPasswordToken;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { LifeBuoy } from 'lucide-react';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
|
||||
|
||||
@@ -8,7 +9,7 @@ import './AuthHeader.styles.scss';
|
||||
|
||||
function AuthHeader(): JSX.Element {
|
||||
const handleGetHelp = useCallback((): void => {
|
||||
window.open('https://signoz.io/support/', '_blank');
|
||||
openExternalLink('https://signoz.io/support/');
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,11 +7,13 @@ import ROUTES from 'constants/routes';
|
||||
import useUpdatedQuery from 'container/GridCardLayout/useResolveQuery';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
export interface NavigateToExplorerProps {
|
||||
filters: TagFilterItem[];
|
||||
@@ -133,7 +135,11 @@ export function useNavigateToExplorer(): (
|
||||
QueryParams.compositeQuery
|
||||
}=${JSONCompositeQuery}`;
|
||||
|
||||
window.open(newExplorerPath, sameTab ? '_self' : '_blank');
|
||||
if (sameTab) {
|
||||
history.push(newExplorerPath);
|
||||
} else {
|
||||
openInNewTab(newExplorerPath);
|
||||
}
|
||||
},
|
||||
[
|
||||
prepareQuery,
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ChevronsDown, ScrollText } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { ChangelogSchema } from 'types/api/changelog/getChangelogByVersion';
|
||||
import { UserPreference } from 'types/api/preferences/preference';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import ChangelogRenderer from './components/ChangelogRenderer';
|
||||
|
||||
@@ -86,11 +87,7 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
|
||||
}, [checkScroll]);
|
||||
|
||||
const onClickUpdateWorkspace = (): void => {
|
||||
window.open(
|
||||
'https://signoz.io/upgrade-path',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
openExternalLink('https://signoz.io/upgrade-path');
|
||||
};
|
||||
|
||||
const onClickScrollForMore = (): void => {
|
||||
|
||||
@@ -10,9 +10,8 @@ import { Skeleton, Tooltip } from 'antd';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import {
|
||||
useCreateResetPasswordToken,
|
||||
getResetPasswordToken,
|
||||
useDeleteUser,
|
||||
useGetResetPasswordToken,
|
||||
useGetUser,
|
||||
useUpdateMyUserV2,
|
||||
useUpdateUser,
|
||||
@@ -56,27 +55,6 @@ function getDeleteTooltip(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getInviteButtonLabel(
|
||||
isLoading: boolean,
|
||||
existingToken: { expiresAt?: Date } | undefined,
|
||||
isExpired: boolean,
|
||||
notFound: boolean,
|
||||
): string {
|
||||
if (isLoading) {
|
||||
return 'Checking invite...';
|
||||
}
|
||||
if (existingToken && !isExpired) {
|
||||
return 'Copy Invite Link';
|
||||
}
|
||||
if (isExpired) {
|
||||
return 'Regenerate Invite Link';
|
||||
}
|
||||
if (notFound) {
|
||||
return 'Generate Invite Link';
|
||||
}
|
||||
return 'Copy Invite Link';
|
||||
}
|
||||
|
||||
function toSaveApiError(err: unknown): APIError {
|
||||
return (
|
||||
convertToApiError(err as AxiosError<RenderErrorResponseDTO>) ??
|
||||
@@ -105,11 +83,9 @@ function EditMemberDrawer({
|
||||
const [localRole, setLocalRole] = useState('');
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [saveErrors, setSaveErrors] = useState<SaveError[]>([]);
|
||||
const [isGeneratingLink, setIsGeneratingLink] = useState(false);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [resetLink, setResetLink] = useState<string | null>(null);
|
||||
const [resetLinkExpiresAt, setResetLinkExpiresAt] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [showResetLinkDialog, setShowResetLinkDialog] = useState(false);
|
||||
const [hasCopiedResetLink, setHasCopiedResetLink] = useState(false);
|
||||
const [linkType, setLinkType] = useState<'invite' | 'reset' | null>(null);
|
||||
@@ -145,27 +121,6 @@ function EditMemberDrawer({
|
||||
applyDiff,
|
||||
} = useMemberRoleManager(member?.id ?? '', open && !!member?.id);
|
||||
|
||||
// Token status query for invited users
|
||||
const {
|
||||
data: tokenQueryData,
|
||||
isLoading: isLoadingTokenStatus,
|
||||
isError: tokenNotFound,
|
||||
} = useGetResetPasswordToken(
|
||||
{ id: member?.id ?? '' },
|
||||
{ query: { enabled: open && !!member?.id && isInvited } },
|
||||
);
|
||||
|
||||
const existingToken = tokenQueryData?.data;
|
||||
const isTokenExpired =
|
||||
existingToken != null &&
|
||||
new Date(String(existingToken.expiresAt)) < new Date();
|
||||
|
||||
// Create/regenerate token mutation
|
||||
const {
|
||||
mutateAsync: createTokenMutation,
|
||||
isLoading: isGeneratingLink,
|
||||
} = useCreateResetPasswordToken();
|
||||
|
||||
const fetchedDisplayName =
|
||||
fetchedUser?.data?.displayName ?? member?.name ?? '';
|
||||
const fetchedUserId = fetchedUser?.data?.id;
|
||||
@@ -383,21 +338,12 @@ function EditMemberDrawer({
|
||||
if (!member) {
|
||||
return;
|
||||
}
|
||||
setIsGeneratingLink(true);
|
||||
try {
|
||||
const response = await createTokenMutation({
|
||||
pathParams: { id: member.id },
|
||||
});
|
||||
const response = await getResetPasswordToken({ id: member.id });
|
||||
if (response?.data?.token) {
|
||||
const link = `${window.location.origin}/password-reset?token=${response.data.token}`;
|
||||
setResetLink(link);
|
||||
setResetLinkExpiresAt(
|
||||
response.data.expiresAt
|
||||
? formatTimezoneAdjustedTimestamp(
|
||||
String(response.data.expiresAt),
|
||||
DATE_TIME_FORMATS.DASH_DATETIME,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
setHasCopiedResetLink(false);
|
||||
setLinkType(isInvited ? 'invite' : 'reset');
|
||||
setShowResetLinkDialog(true);
|
||||
@@ -413,8 +359,10 @@ function EditMemberDrawer({
|
||||
err as AxiosError<RenderErrorResponseDTO, unknown> | null,
|
||||
);
|
||||
showErrorModal(errMsg as APIError);
|
||||
} finally {
|
||||
setIsGeneratingLink(false);
|
||||
}
|
||||
}, [member, isInvited, onClose, showErrorModal, createTokenMutation]);
|
||||
}, [member, isInvited, onClose, showErrorModal]);
|
||||
|
||||
const [copyState, copyToClipboard] = useCopyToClipboard();
|
||||
const handleCopyResetLink = useCallback((): void => {
|
||||
@@ -620,19 +568,12 @@ function EditMemberDrawer({
|
||||
<Button
|
||||
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
|
||||
onClick={handleGenerateResetLink}
|
||||
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
|
||||
disabled={isGeneratingLink || isRootUser}
|
||||
>
|
||||
<RefreshCw size={12} />
|
||||
{isGeneratingLink
|
||||
? 'Generating...'
|
||||
: isInvited
|
||||
? getInviteButtonLabel(
|
||||
isLoadingTokenStatus,
|
||||
existingToken,
|
||||
isTokenExpired,
|
||||
tokenNotFound,
|
||||
)
|
||||
: 'Generate Password Reset Link'}
|
||||
{isGeneratingLink && 'Generating...'}
|
||||
{!isGeneratingLink && isInvited && 'Copy Invite Link'}
|
||||
{!isGeneratingLink && !isInvited && 'Generate Password Reset Link'}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
@@ -682,7 +623,6 @@ function EditMemberDrawer({
|
||||
open={showResetLinkDialog}
|
||||
linkType={linkType}
|
||||
resetLink={resetLink}
|
||||
expiresAt={resetLinkExpiresAt}
|
||||
hasCopied={hasCopiedResetLink}
|
||||
onClose={(): void => {
|
||||
setShowResetLinkDialog(false);
|
||||
|
||||
@@ -6,7 +6,6 @@ interface ResetLinkDialogProps {
|
||||
open: boolean;
|
||||
linkType: 'invite' | 'reset' | null;
|
||||
resetLink: string | null;
|
||||
expiresAt: string | null;
|
||||
hasCopied: boolean;
|
||||
onClose: () => void;
|
||||
onCopy: () => void;
|
||||
@@ -16,7 +15,6 @@ function ResetLinkDialog({
|
||||
open,
|
||||
linkType,
|
||||
resetLink,
|
||||
expiresAt,
|
||||
hasCopied,
|
||||
onClose,
|
||||
onCopy,
|
||||
@@ -55,11 +53,6 @@ function ResetLinkDialog({
|
||||
{hasCopied ? 'Copied!' : 'Copy'}
|
||||
</Button>
|
||||
</div>
|
||||
{expiresAt && (
|
||||
<p className="reset-link-dialog__description">
|
||||
This link expires on {expiresAt}.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</DialogWrapper>
|
||||
);
|
||||
|
||||
@@ -2,9 +2,8 @@ import type { ReactNode } from 'react';
|
||||
import { toast } from '@signozhq/sonner';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
useCreateResetPasswordToken,
|
||||
getResetPasswordToken,
|
||||
useDeleteUser,
|
||||
useGetResetPasswordToken,
|
||||
useGetUser,
|
||||
useSetRoleByUserID,
|
||||
useUpdateMyUserV2,
|
||||
@@ -56,8 +55,7 @@ jest.mock('api/generated/services/users', () => ({
|
||||
useUpdateUser: jest.fn(),
|
||||
useUpdateMyUserV2: jest.fn(),
|
||||
useSetRoleByUserID: jest.fn(),
|
||||
useGetResetPasswordToken: jest.fn(),
|
||||
useCreateResetPasswordToken: jest.fn(),
|
||||
getResetPasswordToken: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
|
||||
@@ -84,7 +82,7 @@ jest.mock('react-use', () => ({
|
||||
const ROLES_ENDPOINT = '*/api/v1/roles';
|
||||
|
||||
const mockDeleteMutate = jest.fn();
|
||||
const mockCreateTokenMutateAsync = jest.fn();
|
||||
const mockGetResetPasswordToken = jest.mocked(getResetPasswordToken);
|
||||
|
||||
const showErrorModal = jest.fn();
|
||||
jest.mock('providers/ErrorModalProvider', () => ({
|
||||
@@ -186,31 +184,6 @@ describe('EditMemberDrawer', () => {
|
||||
mutate: mockDeleteMutate,
|
||||
isLoading: false,
|
||||
});
|
||||
// Token query: valid token for invited members
|
||||
(useGetResetPasswordToken as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
token: 'invite-tok-valid',
|
||||
id: 'token-1',
|
||||
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
// Create token mutation
|
||||
mockCreateTokenMutateAsync.mockResolvedValue({
|
||||
status: 'success',
|
||||
data: {
|
||||
token: 'reset-tok-abc',
|
||||
id: 'user-1',
|
||||
expiresAt: new Date(Date.now() + 86400000).toISOString(),
|
||||
},
|
||||
});
|
||||
(useCreateResetPasswordToken as jest.Mock).mockReturnValue({
|
||||
mutateAsync: mockCreateTokenMutateAsync,
|
||||
isLoading: false,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -384,40 +357,6 @@ describe('EditMemberDrawer', () => {
|
||||
expect(screen.queryByText('Last Modified')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Regenerate Invite Link" when token is expired', () => {
|
||||
(useGetResetPasswordToken as jest.Mock).mockReturnValue({
|
||||
data: {
|
||||
data: {
|
||||
token: 'old-tok',
|
||||
id: 'token-1',
|
||||
expiresAt: new Date(Date.now() - 86400000).toISOString(), // expired yesterday
|
||||
},
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
});
|
||||
|
||||
renderDrawer({ member: invitedMember });
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /regenerate invite link/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Generate Invite Link" when no token exists', () => {
|
||||
(useGetResetPasswordToken as jest.Mock).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
});
|
||||
|
||||
renderDrawer({ member: invitedMember });
|
||||
|
||||
expect(
|
||||
screen.getByRole('button', { name: /generate invite link/i }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls deleteUser after confirming revoke invite for invited members', async () => {
|
||||
const onComplete = jest.fn();
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
@@ -670,7 +609,7 @@ describe('EditMemberDrawer', () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not call createResetPasswordToken when Reset Link is clicked while disabled (root)', async () => {
|
||||
it('does not call getResetPasswordToken when Reset Link is clicked while disabled (root)', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
renderDrawer();
|
||||
|
||||
@@ -678,16 +617,20 @@ describe('EditMemberDrawer', () => {
|
||||
screen.getByRole('button', { name: /generate password reset link/i }),
|
||||
);
|
||||
|
||||
expect(mockCreateTokenMutateAsync).not.toHaveBeenCalled();
|
||||
expect(mockGetResetPasswordToken).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Generate Password Reset Link', () => {
|
||||
beforeEach(() => {
|
||||
mockCopyToClipboard.mockClear();
|
||||
mockGetResetPasswordToken.mockResolvedValue({
|
||||
status: 'success',
|
||||
data: { token: 'reset-tok-abc', id: 'user-1' },
|
||||
});
|
||||
});
|
||||
|
||||
it('calls POST and opens the reset link dialog with the generated link and expiry', async () => {
|
||||
it('calls getResetPasswordToken and opens the reset link dialog with the generated link', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
|
||||
renderDrawer();
|
||||
@@ -699,12 +642,11 @@ describe('EditMemberDrawer', () => {
|
||||
const dialog = await screen.findByRole('dialog', {
|
||||
name: /password reset link/i,
|
||||
});
|
||||
expect(mockCreateTokenMutateAsync).toHaveBeenCalledWith({
|
||||
pathParams: { id: 'user-1' },
|
||||
expect(mockGetResetPasswordToken).toHaveBeenCalledWith({
|
||||
id: 'user-1',
|
||||
});
|
||||
expect(dialog).toBeInTheDocument();
|
||||
expect(dialog).toHaveTextContent('reset-tok-abc');
|
||||
expect(dialog).toHaveTextContent(/this link expires on/i);
|
||||
});
|
||||
|
||||
it('copies the link to clipboard and shows "Copied!" on the button', async () => {
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { VIEW_TYPES, VIEWS } from './constants';
|
||||
@@ -330,10 +331,7 @@ function HostMetricsDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -352,10 +350,7 @@ function HostMetricsDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import { ArrowUpRight } from 'lucide-react';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import './LearnMore.styles.scss';
|
||||
|
||||
@@ -14,7 +15,7 @@ function LearnMore({ text, url, onClick }: LearnMoreProps): JSX.Element {
|
||||
const handleClick = (): void => {
|
||||
onClick?.();
|
||||
if (url) {
|
||||
window.open(url, '_blank');
|
||||
openExternalLink(url);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
KAFKA_SETUP_DOC_LINK,
|
||||
MessagingQueueHealthCheckService,
|
||||
} from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import './MessagingQueueHealthCheck.styles.scss';
|
||||
@@ -76,7 +77,7 @@ function ErrorTitleAndKey({
|
||||
if (isCloudUserVal && !!link) {
|
||||
history.push(link);
|
||||
} else {
|
||||
window.open(KAFKA_SETUP_DOC_LINK, '_blank');
|
||||
openExternalLink(KAFKA_SETUP_DOC_LINK);
|
||||
}
|
||||
};
|
||||
return {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import EmptyQuickFilterIcon from 'assets/CustomIcons/EmptyQuickFilterIcon';
|
||||
import { ArrowUpRight } from 'lucide-react';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
const QUICK_FILTER_DOC_PATHS: Record<string, string> = {
|
||||
severity_text: 'severity-text',
|
||||
@@ -22,9 +23,8 @@ function LogsQuickFilterEmptyState({
|
||||
const handleLearnMoreClick = (): void => {
|
||||
const section = QUICK_FILTER_DOC_PATHS[attributeKey];
|
||||
|
||||
window.open(
|
||||
openExternalLink(
|
||||
`https://signoz.io/docs/logs-management/features/logs-quick-filters#${section}`,
|
||||
'_blank',
|
||||
);
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -37,5 +37,4 @@ export enum LOCALSTORAGE {
|
||||
SHOW_FREQUENCY_CHART = 'SHOW_FREQUENCY_CHART',
|
||||
DISSMISSED_COST_METER_INFO = 'DISMISSED_COST_METER_INFO',
|
||||
DISMISSED_API_KEYS_DEPRECATION_BANNER = 'DISMISSED_API_KEYS_DEPRECATION_BANNER',
|
||||
LICENSE_KEY_CALLOUT_DISMISSED = 'LICENSE_KEY_CALLOUT_DISMISSED',
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from 'container/ApiMonitoring/utils';
|
||||
import { UnfoldVertical } from 'lucide-react';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import emptyStateUrl from '@/assets/Icons/emptyState.svg';
|
||||
|
||||
@@ -107,7 +108,7 @@ function DependentServices({
|
||||
urlQuery.set(QueryParams.startTime, timeRange.startTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, timeRange.endTime.toString());
|
||||
url.search = urlQuery.toString();
|
||||
window.open(url.toString(), '_blank');
|
||||
openInNewTab(`${url.pathname}${url.search}`);
|
||||
},
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import { getOptionList } from './config';
|
||||
import { AlertTypeCard, SelectTypeContainer } from './styles';
|
||||
@@ -55,7 +56,7 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
|
||||
page: 'New alert data source selection page',
|
||||
});
|
||||
|
||||
window.open(url, '_blank');
|
||||
openExternalLink(url);
|
||||
}
|
||||
const renderOptions = useMemo(
|
||||
() => (
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IUser } from 'providers/App/types';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { ROUTING_POLICIES_ROUTE } from './constants';
|
||||
import { RoutingPolicyBannerProps } from './types';
|
||||
@@ -387,7 +388,7 @@ export function NotificationChannelsNotFoundContent({
|
||||
style={{ padding: '0 4px' }}
|
||||
type="link"
|
||||
onClick={(): void => {
|
||||
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
||||
openInNewTab(ROUTES.CHANNELS_NEW);
|
||||
}}
|
||||
>
|
||||
here.
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useGetHosts, usePutHost } from 'api/generated/services/zeus';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import CustomDomainEditModal from './CustomDomainEditModal';
|
||||
|
||||
@@ -48,7 +49,7 @@ function DomainUpdateToast({
|
||||
className="custom-domain-toast-visit-btn"
|
||||
suffixIcon={<ExternalLink size={12} />}
|
||||
onClick={(): void => {
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
openExternalLink(url);
|
||||
}}
|
||||
>
|
||||
Visit new URL
|
||||
|
||||
@@ -16,6 +16,7 @@ import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
|
||||
import { PublicDashboardMetaProps } from 'types/api/dashboard/public/getMeta';
|
||||
import APIError from 'types/api/error';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import './PublicDashboard.styles.scss';
|
||||
|
||||
@@ -294,7 +295,7 @@ function PublicDashboardSetting(): JSX.Element {
|
||||
icon={<ExternalLink size={12} />}
|
||||
onClick={(): void => {
|
||||
if (publicDashboardURL) {
|
||||
window.open(publicDashboardURL, '_blank');
|
||||
openInNewTab(publicDashboardURL);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { AlertDef, Labels } from 'types/api/alerts/def';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import ChannelSelect from './ChannelSelect';
|
||||
@@ -87,7 +88,7 @@ function BasicInfo({
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
ruleId: isNewRule ? 0 : alertDef?.id,
|
||||
});
|
||||
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
||||
openInNewTab(ROUTES.CHANNELS_NEW);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const hasLoggedEvent = useRef(false);
|
||||
|
||||
@@ -46,6 +46,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { compositeQueryToQueryEnvelope } from 'utils/compositeQueryToQueryEnvelope';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import BasicInfo from './BasicInfo';
|
||||
import ChartPreview from './ChartPreview';
|
||||
@@ -771,7 +772,7 @@ function FormAlertRules({
|
||||
queryType: currentQuery.queryType,
|
||||
link: url,
|
||||
});
|
||||
window.open(url, '_blank');
|
||||
openExternalLink(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ import {
|
||||
} from 'types/api/settings/getRetention';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import LicenseRowDismissibleCallout from './LicenseKeyRow/LicenseRowDismissibleCallout/LicenseRowDismissibleCallout';
|
||||
import Retention from './Retention';
|
||||
import StatusMessage from './StatusMessage';
|
||||
import { ActionItemsContainer, ErrorText, ErrorTextContainer } from './styles';
|
||||
@@ -684,12 +683,7 @@ function GeneralSettings({
|
||||
{showCustomDomainSettings && activeLicense?.key && (
|
||||
<div className="custom-domain-card-divider" />
|
||||
)}
|
||||
{activeLicense?.key && (
|
||||
<>
|
||||
<LicenseKeyRow />
|
||||
<LicenseRowDismissibleCallout />
|
||||
</>
|
||||
)}
|
||||
{activeLicense?.key && <LicenseKeyRow />}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
.license-key-callout {
|
||||
margin: var(--spacing-4) var(--spacing-6);
|
||||
width: auto;
|
||||
|
||||
.license-key-callout__description {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--spacing-2);
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.license-key-callout__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: var(--spacing-1) var(--spacing-3);
|
||||
border-radius: 2px;
|
||||
background: var(--callout-primary-background);
|
||||
color: var(--callout-primary-description);
|
||||
font-family: 'SF Mono', 'Fira Code', 'Fira Mono', monospace;
|
||||
font-size: var(--paragraph-base-400-font-size);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: var(--callout-primary-border);
|
||||
color: var(--callout-primary-icon);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import './LicenseRowDismissible.styles.scss';
|
||||
|
||||
function LicenseRowDismissibleCallout(): JSX.Element | null {
|
||||
const [isCalloutDismissed, setIsCalloutDismissed] = useState<boolean>(
|
||||
() =>
|
||||
getLocalStorageApi(LOCALSTORAGE.LICENSE_KEY_CALLOUT_DISMISSED) === 'true',
|
||||
);
|
||||
|
||||
const { user, featureFlags } = useAppContext();
|
||||
const { isCloudUser } = useGetTenantLicense();
|
||||
|
||||
const isAdmin = user.role === USER_ROLES.ADMIN;
|
||||
const isEditor = user.role === USER_ROLES.EDITOR;
|
||||
|
||||
const isGatewayEnabled =
|
||||
featureFlags?.find((feature) => feature.name === FeatureKeys.GATEWAY)
|
||||
?.active || false;
|
||||
|
||||
const hasServiceAccountsAccess = isAdmin;
|
||||
|
||||
const hasIngestionAccess =
|
||||
(isCloudUser && !isGatewayEnabled) ||
|
||||
(isGatewayEnabled && (isAdmin || isEditor));
|
||||
|
||||
const handleDismissCallout = (): void => {
|
||||
setLocalStorageApi(LOCALSTORAGE.LICENSE_KEY_CALLOUT_DISMISSED, 'true');
|
||||
setIsCalloutDismissed(true);
|
||||
};
|
||||
|
||||
return !isCalloutDismissed ? (
|
||||
<Callout
|
||||
type="info"
|
||||
size="small"
|
||||
showIcon
|
||||
dismissable
|
||||
onClose={handleDismissCallout}
|
||||
className="license-key-callout"
|
||||
description={
|
||||
<div className="license-key-callout__description">
|
||||
This is <strong>NOT</strong> your ingestion or Service account key.
|
||||
{(hasServiceAccountsAccess || hasIngestionAccess) && (
|
||||
<>
|
||||
{' '}
|
||||
Find your{' '}
|
||||
{hasServiceAccountsAccess && (
|
||||
<Link
|
||||
to={ROUTES.SERVICE_ACCOUNTS_SETTINGS}
|
||||
className="license-key-callout__link"
|
||||
>
|
||||
Service account here
|
||||
</Link>
|
||||
)}
|
||||
{hasServiceAccountsAccess && hasIngestionAccess && ' and '}
|
||||
{hasIngestionAccess && (
|
||||
<Link
|
||||
to={ROUTES.INGESTION_SETTINGS}
|
||||
className="license-key-callout__link"
|
||||
>
|
||||
Ingestion key here
|
||||
</Link>
|
||||
)}
|
||||
.
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export default LicenseRowDismissibleCallout;
|
||||
@@ -1,229 +0,0 @@
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import LicenseRowDismissibleCallout from '../LicenseRowDismissibleCallout';
|
||||
|
||||
const getDescription = (): HTMLElement =>
|
||||
screen.getByText(
|
||||
(_, el) =>
|
||||
el?.classList?.contains('license-key-callout__description') ?? false,
|
||||
);
|
||||
|
||||
const queryDescription = (): HTMLElement | null =>
|
||||
screen.queryByText(
|
||||
(_, el) =>
|
||||
el?.classList?.contains('license-key-callout__description') ?? false,
|
||||
);
|
||||
|
||||
jest.mock('hooks/useGetTenantLicense', () => ({
|
||||
useGetTenantLicense: jest.fn(),
|
||||
}));
|
||||
|
||||
const mockLicense = (isCloudUser: boolean): void => {
|
||||
(useGetTenantLicense as jest.Mock).mockReturnValue({
|
||||
isCloudUser,
|
||||
isEnterpriseSelfHostedUser: !isCloudUser,
|
||||
isCommunityUser: false,
|
||||
isCommunityEnterpriseUser: false,
|
||||
});
|
||||
};
|
||||
|
||||
const renderCallout = (
|
||||
role: string,
|
||||
isCloudUser: boolean,
|
||||
gatewayActive: boolean,
|
||||
): void => {
|
||||
mockLicense(isCloudUser);
|
||||
render(
|
||||
<LicenseRowDismissibleCallout />,
|
||||
{},
|
||||
{
|
||||
role,
|
||||
appContextOverrides: {
|
||||
featureFlags: [
|
||||
{
|
||||
name: FeatureKeys.GATEWAY,
|
||||
active: gatewayActive,
|
||||
usage: 0,
|
||||
usage_limit: -1,
|
||||
route: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
describe('LicenseRowDismissibleCallout', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('callout content per access level', () => {
|
||||
it.each([
|
||||
{
|
||||
scenario: 'viewer, non-cloud, gateway off — base text only, no links',
|
||||
role: USER_ROLES.VIEWER,
|
||||
isCloudUser: false,
|
||||
gatewayActive: false,
|
||||
serviceAccountLink: false,
|
||||
ingestionLink: false,
|
||||
expectedText: 'This is NOT your ingestion or Service account key.',
|
||||
},
|
||||
{
|
||||
scenario: 'admin, non-cloud, gateway off — service accounts link only',
|
||||
role: USER_ROLES.ADMIN,
|
||||
isCloudUser: false,
|
||||
gatewayActive: false,
|
||||
serviceAccountLink: true,
|
||||
ingestionLink: false,
|
||||
expectedText:
|
||||
'This is NOT your ingestion or Service account key. Find your Service account here.',
|
||||
},
|
||||
{
|
||||
scenario: 'viewer, cloud, gateway off — ingestion link only',
|
||||
role: USER_ROLES.VIEWER,
|
||||
isCloudUser: true,
|
||||
gatewayActive: false,
|
||||
serviceAccountLink: false,
|
||||
ingestionLink: true,
|
||||
expectedText:
|
||||
'This is NOT your ingestion or Service account key. Find your Ingestion key here.',
|
||||
},
|
||||
{
|
||||
scenario: 'admin, cloud, gateway off — both links',
|
||||
role: USER_ROLES.ADMIN,
|
||||
isCloudUser: true,
|
||||
gatewayActive: false,
|
||||
serviceAccountLink: true,
|
||||
ingestionLink: true,
|
||||
expectedText:
|
||||
'This is NOT your ingestion or Service account key. Find your Service account here and Ingestion key here.',
|
||||
},
|
||||
{
|
||||
scenario: 'admin, non-cloud, gateway on — both links',
|
||||
role: USER_ROLES.ADMIN,
|
||||
isCloudUser: false,
|
||||
gatewayActive: true,
|
||||
serviceAccountLink: true,
|
||||
ingestionLink: true,
|
||||
expectedText:
|
||||
'This is NOT your ingestion or Service account key. Find your Service account here and Ingestion key here.',
|
||||
},
|
||||
{
|
||||
scenario: 'editor, non-cloud, gateway on — ingestion link only',
|
||||
role: USER_ROLES.EDITOR,
|
||||
isCloudUser: false,
|
||||
gatewayActive: true,
|
||||
serviceAccountLink: false,
|
||||
ingestionLink: true,
|
||||
expectedText:
|
||||
'This is NOT your ingestion or Service account key. Find your Ingestion key here.',
|
||||
},
|
||||
{
|
||||
scenario: 'editor, cloud, gateway off — ingestion link only',
|
||||
role: USER_ROLES.EDITOR,
|
||||
isCloudUser: true,
|
||||
gatewayActive: false,
|
||||
serviceAccountLink: false,
|
||||
ingestionLink: true,
|
||||
expectedText:
|
||||
'This is NOT your ingestion or Service account key. Find your Ingestion key here.',
|
||||
},
|
||||
])(
|
||||
'$scenario',
|
||||
({
|
||||
role,
|
||||
isCloudUser,
|
||||
gatewayActive,
|
||||
serviceAccountLink,
|
||||
ingestionLink,
|
||||
expectedText,
|
||||
}) => {
|
||||
renderCallout(role, isCloudUser, gatewayActive);
|
||||
|
||||
const description = getDescription();
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(description).toHaveTextContent(expectedText);
|
||||
|
||||
if (serviceAccountLink) {
|
||||
expect(
|
||||
screen.getByRole('link', { name: /Service account here/ }),
|
||||
).toBeInTheDocument();
|
||||
} else {
|
||||
expect(
|
||||
screen.queryByRole('link', { name: /Service account here/ }),
|
||||
).not.toBeInTheDocument();
|
||||
}
|
||||
|
||||
if (ingestionLink) {
|
||||
expect(
|
||||
screen.getByRole('link', { name: /Ingestion key here/ }),
|
||||
).toBeInTheDocument();
|
||||
} else {
|
||||
expect(
|
||||
screen.queryByRole('link', { name: /Ingestion key here/ }),
|
||||
).not.toBeInTheDocument();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('Link routing', () => {
|
||||
it('should link to service accounts settings', () => {
|
||||
renderCallout(USER_ROLES.ADMIN, false, false);
|
||||
|
||||
const link = screen.getByRole('link', {
|
||||
name: /Service account here/,
|
||||
}) as HTMLAnchorElement;
|
||||
|
||||
expect(link.getAttribute('href')).toBe(ROUTES.SERVICE_ACCOUNTS_SETTINGS);
|
||||
});
|
||||
|
||||
it('should link to ingestion settings', () => {
|
||||
renderCallout(USER_ROLES.VIEWER, true, false);
|
||||
|
||||
const link = screen.getByRole('link', {
|
||||
name: /Ingestion key here/,
|
||||
}) as HTMLAnchorElement;
|
||||
|
||||
expect(link.getAttribute('href')).toBe(ROUTES.INGESTION_SETTINGS);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dismissal functionality', () => {
|
||||
it('should hide callout when dismiss button is clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderCallout(USER_ROLES.ADMIN, false, false);
|
||||
|
||||
expect(getDescription()).toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByRole('button'));
|
||||
|
||||
expect(queryDescription()).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should persist dismissal in localStorage', async () => {
|
||||
const user = userEvent.setup();
|
||||
renderCallout(USER_ROLES.ADMIN, false, false);
|
||||
|
||||
await user.click(screen.getByRole('button'));
|
||||
|
||||
expect(
|
||||
localStorage.getItem(LOCALSTORAGE.LICENSE_KEY_CALLOUT_DISMISSED),
|
||||
).toBe('true');
|
||||
});
|
||||
|
||||
it('should not render when localStorage dismissal is set', () => {
|
||||
localStorage.setItem(LOCALSTORAGE.LICENSE_KEY_CALLOUT_DISMISSED, 'true');
|
||||
renderCallout(USER_ROLES.ADMIN, false, false);
|
||||
|
||||
expect(queryDescription()).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,7 @@ import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import beaconUrl from '@/assets/Icons/beacon.svg';
|
||||
|
||||
@@ -103,11 +104,7 @@ export default function AlertRules({
|
||||
source: 'Alert Rules',
|
||||
});
|
||||
|
||||
window.open(
|
||||
'https://signoz.io/docs/alerts/',
|
||||
'_blank',
|
||||
'noreferrer noopener',
|
||||
);
|
||||
openExternalLink('https://signoz.io/docs/alerts/');
|
||||
}}
|
||||
>
|
||||
Learn more <ArrowUpRight size={12} />
|
||||
|
||||
@@ -10,6 +10,7 @@ import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openExternalLink, openInNewTab } from 'utils/navigation';
|
||||
|
||||
import dialsUrl from '@/assets/Icons/dials.svg';
|
||||
|
||||
@@ -87,10 +88,7 @@ export default function Dashboards({
|
||||
logEvent('Homepage: Learn more clicked', {
|
||||
source: 'Dashboards',
|
||||
});
|
||||
window.open(
|
||||
'https://signoz.io/docs/userguide/manage-dashboards/',
|
||||
'_blank',
|
||||
);
|
||||
openExternalLink('https://signoz.io/docs/userguide/manage-dashboards/');
|
||||
}}
|
||||
>
|
||||
Learn more <ArrowUpRight size={12} />
|
||||
@@ -114,7 +112,7 @@ export default function Dashboards({
|
||||
dashboardName: dashboard.data.title,
|
||||
});
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(getLink(), '_blank');
|
||||
openInNewTab(getLink());
|
||||
} else {
|
||||
safeNavigate(getLink());
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Link2 } from 'lucide-react';
|
||||
import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import containerPlusUrl from '@/assets/Icons/container-plus.svg';
|
||||
import helloWaveUrl from '@/assets/Icons/hello-wave.svg';
|
||||
@@ -51,7 +52,7 @@ function DataSourceInfo({
|
||||
if (activeLicense && activeLicense.platform === LicensePlatform.CLOUD) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(DOCS_LINKS.ADD_DATA_SOURCE, '_blank', 'noopener noreferrer');
|
||||
openExternalLink(DOCS_LINKS.ADD_DATA_SOURCE);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ArrowRight, ArrowRightToLine, BookOpenText } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import './HomeChecklist.styles.scss';
|
||||
|
||||
@@ -99,11 +100,7 @@ function HomeChecklist({
|
||||
) {
|
||||
history.push(item.toRoute || '');
|
||||
} else {
|
||||
window?.open(
|
||||
item.docsLink || '',
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
openExternalLink(item.docsLink || '');
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -119,7 +116,7 @@ function HomeChecklist({
|
||||
step: item.id,
|
||||
});
|
||||
|
||||
window?.open(item.docsLink, '_blank', 'noopener noreferrer');
|
||||
openExternalLink(item.docsLink || '');
|
||||
}}
|
||||
>
|
||||
<BookOpenText size={16} />
|
||||
|
||||
@@ -19,12 +19,13 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { ViewProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import circusTentUrl from '@/assets/Icons/circus-tent.svg';
|
||||
import eightBallUrl from '@/assets/Icons/eight-ball.svg';
|
||||
import floppyDiscUrl from '@/assets/Icons/floppy-disc.svg';
|
||||
import logsUrl from '@/assets/Icons/logs.svg';
|
||||
|
||||
import { getItemIcon } from '../constants';
|
||||
|
||||
export default function SavedViews({
|
||||
onUpdateChecklistDoneItem,
|
||||
loadingUserPreferences,
|
||||
@@ -196,11 +197,7 @@ export default function SavedViews({
|
||||
entity: selectedEntity,
|
||||
});
|
||||
|
||||
window.open(
|
||||
'https://signoz.io/docs/product-features/saved-view/',
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
openExternalLink('https://signoz.io/docs/product-features/saved-view/');
|
||||
}}
|
||||
>
|
||||
Learn more <ArrowUpRight size={12} />
|
||||
@@ -229,7 +226,7 @@ export default function SavedViews({
|
||||
>
|
||||
<div className="saved-view-item-name-container home-data-item-name-container">
|
||||
<img
|
||||
src={getItemIcon(String(view.id))}
|
||||
src={view.id % 2 === 0 ? eightBallUrl : circusTentUrl}
|
||||
alt="alert-rules"
|
||||
className="alert-rules-img"
|
||||
/>
|
||||
|
||||
@@ -31,6 +31,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
|
||||
|
||||
@@ -79,11 +80,7 @@ const EmptyState = memo(
|
||||
) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(
|
||||
DOCS_LINKS.ADD_DATA_SOURCE,
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
openExternalLink(DOCS_LINKS.ADD_DATA_SOURCE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -97,10 +94,7 @@ const EmptyState = memo(
|
||||
logEvent('Homepage: Learn more clicked', {
|
||||
source: 'Service Metrics',
|
||||
});
|
||||
window.open(
|
||||
'https://signoz.io/docs/instrumentation/overview/',
|
||||
'_blank',
|
||||
);
|
||||
openExternalLink('https://signoz.io/docs/instrumentation/overview/');
|
||||
}}
|
||||
>
|
||||
Learn more <ArrowUpRight size={12} />
|
||||
|
||||
@@ -17,6 +17,7 @@ import { ServicesList } from 'types/api/metrics/getService';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
|
||||
|
||||
@@ -133,11 +134,7 @@ export default function ServiceTraces({
|
||||
) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(
|
||||
DOCS_LINKS.ADD_DATA_SOURCE,
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
openExternalLink(DOCS_LINKS.ADD_DATA_SOURCE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -151,10 +148,7 @@ export default function ServiceTraces({
|
||||
logEvent('Homepage: Learn more clicked', {
|
||||
source: 'Service Traces',
|
||||
});
|
||||
window.open(
|
||||
'https://signoz.io/docs/instrumentation/overview/',
|
||||
'_blank',
|
||||
);
|
||||
openExternalLink('https://signoz.io/docs/instrumentation/overview/');
|
||||
}}
|
||||
>
|
||||
Learn more <ArrowUpRight size={12} />
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import ClusterEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||
@@ -414,10 +415,7 @@ function ClusterDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -436,10 +434,7 @@ function ClusterDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import DaemonSetEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||
@@ -429,10 +430,7 @@ function DaemonSetDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -451,10 +449,7 @@ function DaemonSetDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import DeploymentEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||
@@ -433,10 +434,7 @@ function DeploymentDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -455,10 +453,7 @@ function DeploymentDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import JobEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||
@@ -427,10 +428,7 @@ function JobDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -449,10 +447,7 @@ function JobDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import NamespaceEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||
@@ -419,10 +420,7 @@ function NamespaceDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -441,10 +439,7 @@ function NamespaceDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import NodeLogs from '../../EntityDetailsUtils/EntityLogs';
|
||||
@@ -416,10 +417,7 @@ function NodeDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -438,10 +436,7 @@ function NodeDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import PodEvents from '../../EntityDetailsUtils/EntityEvents';
|
||||
@@ -435,10 +436,7 @@ function PodDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -457,10 +455,7 @@ function PodDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import {
|
||||
@@ -431,10 +432,7 @@ function StatefulSetDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -453,10 +451,7 @@ function StatefulSetDetails({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
interface AlertInfoCardProps {
|
||||
header: string;
|
||||
@@ -19,7 +20,7 @@ function AlertInfoCard({
|
||||
className="alert-info-card"
|
||||
onClick={(): void => {
|
||||
onClick();
|
||||
window.open(link, '_blank');
|
||||
openExternalLink(link);
|
||||
}}
|
||||
>
|
||||
<div className="alert-card-text">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ArrowRightOutlined, PlayCircleFilled } from '@ant-design/icons';
|
||||
import { Flex, Typography } from 'antd';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
interface InfoLinkTextProps {
|
||||
infoText: string;
|
||||
@@ -20,7 +21,7 @@ function InfoLinkText({
|
||||
<Flex
|
||||
onClick={(): void => {
|
||||
onClick();
|
||||
window.open(link, '_blank');
|
||||
openExternalLink(link);
|
||||
}}
|
||||
className="info-link-container"
|
||||
>
|
||||
|
||||
@@ -83,6 +83,7 @@ import {
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { openExternalLink, openInNewTab } from 'utils/navigation';
|
||||
|
||||
import awwSnapUrl from '@/assets/Icons/awwSnap.svg';
|
||||
import dashboardsUrl from '@/assets/Icons/dashboards.svg';
|
||||
@@ -457,7 +458,7 @@ function DashboardsList(): JSX.Element {
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
window.open(getLink(), '_blank');
|
||||
openInNewTab(getLink());
|
||||
}}
|
||||
>
|
||||
Open in New Tab
|
||||
@@ -739,9 +740,8 @@ function DashboardsList(): JSX.Element {
|
||||
className="learn-more"
|
||||
data-testid="learn-more"
|
||||
onClick={(): void => {
|
||||
window.open(
|
||||
openExternalLink(
|
||||
'https://signoz.io/docs/userguide/manage-dashboards?utm_source=product&utm_medium=dashboard-list-empty-state',
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LockFilled } from '@ant-design/icons';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { Data } from '../DashboardsList';
|
||||
import { TableLinkText } from './styles';
|
||||
@@ -12,7 +13,7 @@ function Name(name: Data['name'], data: Data): JSX.Element {
|
||||
|
||||
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(getLink(), '_blank');
|
||||
openInNewTab(getLink());
|
||||
} else {
|
||||
history.push(getLink());
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { useContextLogData } from './useContextLogData';
|
||||
|
||||
@@ -116,7 +117,7 @@ function ContextLogRenderer({
|
||||
);
|
||||
|
||||
const link = `${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`;
|
||||
window.open(link, '_blank', 'noopener,noreferrer');
|
||||
openInNewTab(link);
|
||||
},
|
||||
[query, urlQuery],
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { ActionItemProps } from './ActionItem';
|
||||
import FieldRenderer from './FieldRenderer';
|
||||
@@ -191,7 +192,7 @@ function TableView({
|
||||
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
// open the trace in new tab
|
||||
window.open(route, '_blank');
|
||||
openInNewTab(route);
|
||||
} else {
|
||||
history.push(route);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Typography } from 'antd';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import history from 'lib/history';
|
||||
import { ArrowRight } from 'lucide-react';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import awwSnapUrl from '@/assets/Icons/awwSnap.svg';
|
||||
|
||||
@@ -14,7 +15,7 @@ export default function LogsError(): JSX.Element {
|
||||
if (isCloudUserVal) {
|
||||
history.push('/support');
|
||||
} else {
|
||||
window.open('https://signoz.io/slack', '_blank');
|
||||
openExternalLink('https://signoz.io/slack');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { openInNewTab as openInNewTabFn } from 'utils/navigation';
|
||||
|
||||
import { TopOperationList } from './TopOperationsTable';
|
||||
import { NavigateToTraceProps } from './types';
|
||||
@@ -37,7 +38,7 @@ export const navigateToTrace = ({
|
||||
}=${JSONCompositeQuery}`;
|
||||
|
||||
if (openInNewTab) {
|
||||
window.open(newTraceExplorerPath, '_blank');
|
||||
openInNewTabFn(newTraceExplorerPath);
|
||||
} else {
|
||||
safeNavigate(newTraceExplorerPath);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { Bell, Grid } from 'lucide-react';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { pluralize } from 'utils/pluralize';
|
||||
|
||||
import { DashboardsAndAlertsPopoverProps } from './types';
|
||||
@@ -67,9 +68,8 @@ function DashboardsAndAlertsPopover({
|
||||
<Typography.Link
|
||||
key={alert.alertId}
|
||||
onClick={(): void => {
|
||||
window.open(
|
||||
openInNewTab(
|
||||
`${ROUTES.ALERT_OVERVIEW}?${QueryParams.ruleId}=${alert.alertId}`,
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
className="dashboards-popover-content-item"
|
||||
@@ -90,11 +90,8 @@ function DashboardsAndAlertsPopover({
|
||||
<Typography.Link
|
||||
key={dashboard.dashboardId}
|
||||
onClick={(): void => {
|
||||
window.open(
|
||||
generatePath(ROUTES.DASHBOARD, {
|
||||
dashboardId: dashboard.dashboardId,
|
||||
}),
|
||||
'_blank',
|
||||
openInNewTab(
|
||||
generatePath(ROUTES.DASHBOARD, { dashboardId: dashboard.dashboardId }),
|
||||
);
|
||||
}}
|
||||
className="dashboards-popover-content-item"
|
||||
|
||||
@@ -2,10 +2,8 @@ import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Input, Modal, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import {
|
||||
updateMyPassword,
|
||||
useUpdateMyUserV2,
|
||||
} from 'api/generated/services/users';
|
||||
import { useUpdateMyUserV2 } from 'api/generated/services/users';
|
||||
import changeMyPassword from 'api/v1/factor_password/changeMyPassword';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { Check, FileTerminal, MailIcon, UserIcon } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
@@ -55,9 +53,10 @@ function UserInfo(): JSX.Element {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
await updateMyPassword({
|
||||
await changeMyPassword({
|
||||
newPassword: updatePassword,
|
||||
oldPassword: currentPassword,
|
||||
userId: user.id,
|
||||
});
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
|
||||
@@ -6,6 +6,7 @@ import history from 'lib/history';
|
||||
import { ArrowUpRight } from 'lucide-react';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import DOCLINKS from 'utils/docLinks';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import eyesEmojiUrl from '@/assets/Images/eyesEmoji.svg';
|
||||
|
||||
@@ -42,11 +43,11 @@ export default function NoLogs({
|
||||
}
|
||||
history.push(link);
|
||||
} else if (dataSource === 'traces') {
|
||||
window.open(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE, '_blank');
|
||||
openExternalLink(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE);
|
||||
} else if (dataSource === DataSource.METRICS) {
|
||||
window.open(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE, '_blank');
|
||||
openExternalLink(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE);
|
||||
} else {
|
||||
window.open(`${DOCLINKS.USER_GUIDE}${dataSource}/`, '_blank');
|
||||
openExternalLink(`${DOCLINKS.USER_GUIDE}${dataSource}/`);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// hippa.svg and soc2.svg do not exist in src/assets — suppressed until assets are added
|
||||
import { Dot } from 'lucide-react';
|
||||
|
||||
import './OnboardingFooter.styles.scss';
|
||||
@@ -12,7 +13,6 @@ export function OnboardingFooter(): JSX.Element {
|
||||
className="footer-content"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{/* hippa.svg does not exist in src/assets — suppressed until asset is added */}
|
||||
{/* eslint-disable-next-line rulesdir/no-unsupported-asset-pattern */}
|
||||
<img src="/logos/hippa.svg" alt="HIPPA" className="footer-logo" />
|
||||
<span className="footer-text">HIPPA</span>
|
||||
@@ -24,7 +24,6 @@ export function OnboardingFooter(): JSX.Element {
|
||||
className="footer-content"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{/* soc2.svg does not exist in src/assets — suppressed until asset is added */}
|
||||
{/* eslint-disable-next-line rulesdir/no-unsupported-asset-pattern */}
|
||||
<img src="/logos/soc2.svg" alt="SOC2" className="footer-logo" />
|
||||
<span className="footer-text">SOC2</span>
|
||||
|
||||
@@ -115,6 +115,7 @@ const useBaseAggregateOptions = ({
|
||||
key={id}
|
||||
icon={<LinkOutlined />}
|
||||
onClick={(): void => {
|
||||
// eslint-disable-next-line no-restricted-syntax -- context links can be internal or external URLs provided by users
|
||||
window.open(url, '_blank');
|
||||
onClose?.();
|
||||
}}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ModalTitle } from 'container/PipelinePage/PipelineListsView/styles';
|
||||
import { Check, Loader, X } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { INITIAL_ROUTING_POLICY_DETAILS_FORM_STATE } from './constants';
|
||||
import {
|
||||
@@ -76,7 +77,7 @@ function RoutingPolicyDetails({
|
||||
style={{ padding: '0 4px' }}
|
||||
type="link"
|
||||
onClick={(): void => {
|
||||
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
||||
openInNewTab(ROUTES.CHANNELS_NEW);
|
||||
}}
|
||||
>
|
||||
here.
|
||||
|
||||
@@ -64,7 +64,7 @@ import { USER_ROLES } from 'types/roles';
|
||||
import { checkVersionState } from 'utils/app';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { showErrorNotification } from 'utils/error';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { openExternalLink, openInNewTab } from 'utils/navigation';
|
||||
|
||||
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
|
||||
|
||||
@@ -818,7 +818,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
);
|
||||
|
||||
if (item && !('type' in item) && item.isExternal && item.url) {
|
||||
window.open(item.url, '_blank');
|
||||
openExternalLink(item.url);
|
||||
}
|
||||
|
||||
const event = (info as SidebarItem & { domEvent?: MouseEvent }).domEvent;
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import noDataUrl from '@/assets/Icons/no-data.svg';
|
||||
@@ -143,7 +144,7 @@ function SpanLogs({
|
||||
|
||||
const url = `${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`;
|
||||
|
||||
window.open(url, '_blank');
|
||||
openInNewTab(url);
|
||||
},
|
||||
[
|
||||
isLogSpanRelated,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { BarChart2, Compass, X } from 'lucide-react';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
import { DataSource, LogsAggregatorOperator } from 'types/common/queryBuilder';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { RelatedSignalsViews } from '../constants';
|
||||
import SpanLogs from '../SpanLogs/SpanLogs';
|
||||
@@ -157,13 +158,7 @@ function SpanRelatedSignals({
|
||||
searchParams.set(QueryParams.startTime, startTimeMs.toString());
|
||||
searchParams.set(QueryParams.endTime, endTimeMs.toString());
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${
|
||||
ROUTES.LOGS_EXPLORER
|
||||
}?${searchParams.toString()}`,
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${searchParams.toString()}`);
|
||||
}, [selectedSpan.traceId, traceStartTime, traceEndTime]);
|
||||
|
||||
const emptyStateConfig = useMemo(
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
UPDATE_SPANS_AGGREGATE_PAGE_SIZE,
|
||||
} from 'types/actions/trace';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
dayjs.extend(duration);
|
||||
@@ -214,7 +215,7 @@ function TraceTable(): JSX.Element {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(getLink(record), '_blank');
|
||||
openInNewTab(getLink(record));
|
||||
} else {
|
||||
history.push(getLink(record));
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
import { toFixed } from 'utils/toFixed';
|
||||
|
||||
import funnelAddUrl from '@/assets/Icons/funnel-add.svg';
|
||||
@@ -547,12 +548,11 @@ function Success(props: ISuccessProps): JSX.Element {
|
||||
icon={<ArrowUpRight size={14} />}
|
||||
className="right-info"
|
||||
type="text"
|
||||
onClick={(): WindowProxy | null =>
|
||||
window.open(
|
||||
onClick={(): void => {
|
||||
openExternalLink(
|
||||
'https://signoz.io/docs/userguide/traces/#missing-spans',
|
||||
'_blank',
|
||||
)
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
Learn More
|
||||
</Button>
|
||||
|
||||
@@ -28,6 +28,7 @@ import { useTimezone } from 'providers/Timezone';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import './TracesTableComponent.styles.scss';
|
||||
|
||||
@@ -86,7 +87,7 @@ function TracesTableComponent({
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(getTraceLink(record), '_blank');
|
||||
openInNewTab(getTraceLink(record));
|
||||
} else {
|
||||
history.push(getTraceLink(record));
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
ConnectionUrlResponse,
|
||||
GenerateConnectionUrlPayload,
|
||||
} from 'types/api/integrations/aws';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
import { regions } from 'utils/regions';
|
||||
|
||||
import logEvent from '../../../api/common/logEvent';
|
||||
@@ -120,7 +121,7 @@ export function useIntegrationModal({
|
||||
logEvent('AWS Integration: Account connection attempt redirected to AWS', {
|
||||
id: data.account_id,
|
||||
});
|
||||
window.open(data.connection_url, '_blank');
|
||||
openExternalLink(data.connection_url);
|
||||
setModalState(ModalStateEnum.WAITING);
|
||||
setAccountId(data.account_id);
|
||||
},
|
||||
|
||||
@@ -22,6 +22,7 @@ import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getGraphType } from 'utils/getGraphType';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
|
||||
const queryRangeMutation = useMutation(getSubstituteVars);
|
||||
@@ -92,7 +93,7 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
|
||||
|
||||
const url = `${ROUTES.ALERTS_NEW}?${params.toString()}`;
|
||||
|
||||
window.open(url, '_blank', 'noreferrer');
|
||||
openInNewTab(url);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
|
||||
90
frontend/src/hooks/useSafeNavigate.test.ts
Normal file
90
frontend/src/hooks/useSafeNavigate.test.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom-v5-compat';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
jest.mock('lib/history', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
createHref: jest.fn(
|
||||
({
|
||||
pathname,
|
||||
search,
|
||||
hash,
|
||||
}: {
|
||||
pathname: string;
|
||||
search?: string;
|
||||
hash?: string;
|
||||
}) => `/signoz${pathname}${search || ''}${hash || ''}`,
|
||||
),
|
||||
},
|
||||
}));
|
||||
|
||||
import history from 'lib/history';
|
||||
|
||||
import { useSafeNavigate } from './useSafeNavigate';
|
||||
|
||||
function renderSafeNavigate(): ReturnType<typeof useSafeNavigate> {
|
||||
const { result } = renderHook(() => useSafeNavigate(), {
|
||||
wrapper: ({ children }: { children: React.ReactNode }) =>
|
||||
React.createElement(MemoryRouter, { initialEntries: ['/home'] }, children),
|
||||
});
|
||||
return result.current;
|
||||
}
|
||||
|
||||
describe('useSafeNavigate — newTab option', () => {
|
||||
let windowOpenSpy: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
windowOpenSpy = jest.spyOn(window, 'open').mockImplementation(jest.fn());
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
windowOpenSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('opens string path in new tab with base path prepended', () => {
|
||||
const { safeNavigate } = renderSafeNavigate();
|
||||
safeNavigate('/traces', { newTab: true });
|
||||
expect(windowOpenSpy).toHaveBeenCalledWith('/signoz/traces', '_blank');
|
||||
expect(history.createHref).toHaveBeenCalledWith({
|
||||
pathname: '/traces',
|
||||
search: '',
|
||||
hash: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves query string when opening in new tab', () => {
|
||||
const { safeNavigate } = renderSafeNavigate();
|
||||
safeNavigate('/traces?service=api', { newTab: true });
|
||||
expect(windowOpenSpy).toHaveBeenCalledWith(
|
||||
'/signoz/traces?service=api',
|
||||
'_blank',
|
||||
);
|
||||
expect(history.createHref).toHaveBeenCalledWith({
|
||||
pathname: '/traces',
|
||||
search: '?service=api',
|
||||
hash: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('opens object-form path in new tab with base path prepended', () => {
|
||||
const { safeNavigate } = renderSafeNavigate();
|
||||
safeNavigate({ pathname: '/logs', search: '?env=prod' }, { newTab: true });
|
||||
expect(windowOpenSpy).toHaveBeenCalledWith('/signoz/logs?env=prod', '_blank');
|
||||
expect(history.createHref).toHaveBeenCalledWith({
|
||||
pathname: '/logs',
|
||||
search: '?env=prod',
|
||||
});
|
||||
});
|
||||
|
||||
it('passes external URL directly to window.open without createHref', () => {
|
||||
const { safeNavigate } = renderSafeNavigate();
|
||||
safeNavigate('https://docs.signoz.io/page', { newTab: true });
|
||||
expect(windowOpenSpy).toHaveBeenCalledWith(
|
||||
'https://docs.signoz.io/page',
|
||||
'_blank',
|
||||
);
|
||||
expect(history.createHref).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom-v5-compat';
|
||||
import history from 'lib/history';
|
||||
import { cloneDeep, isEqual } from 'lodash-es';
|
||||
|
||||
interface NavigateOptions {
|
||||
@@ -126,11 +127,30 @@ export const useSafeNavigate = (
|
||||
const shouldOpenInNewTab = options?.newTab;
|
||||
|
||||
if (shouldOpenInNewTab) {
|
||||
const targetPath =
|
||||
typeof to === 'string'
|
||||
? to
|
||||
: `${to.pathname || location.pathname}${to.search || ''}`;
|
||||
window.open(targetPath, '_blank');
|
||||
if (!to) {
|
||||
return;
|
||||
}
|
||||
let href: string;
|
||||
if (typeof to === 'string') {
|
||||
// 'to' may include a query string; parse before passing to createHref
|
||||
// so search params are not embedded in pathname.
|
||||
const parsed = new URL(to, window.location.origin);
|
||||
if (parsed.origin !== window.location.origin) {
|
||||
window.open(to, '_blank');
|
||||
return;
|
||||
}
|
||||
href = history.createHref({
|
||||
pathname: parsed.pathname,
|
||||
search: parsed.search,
|
||||
hash: parsed.hash,
|
||||
});
|
||||
} else {
|
||||
href = history.createHref({
|
||||
pathname: to.pathname ?? location.pathname,
|
||||
search: to.search ?? '',
|
||||
});
|
||||
}
|
||||
window.open(href, '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
39
frontend/src/lib/basePath.test.ts
Normal file
39
frontend/src/lib/basePath.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { getBasePath, readBasePath } from './basePath';
|
||||
|
||||
describe('readBasePath', () => {
|
||||
afterEach(() => {
|
||||
document.querySelectorAll('base').forEach((el) => el.remove());
|
||||
});
|
||||
|
||||
it('returns "/" when no <base> tag is present', () => {
|
||||
expect(readBasePath()).toBe('/');
|
||||
});
|
||||
|
||||
it('returns the href when <base href="/signoz/"> exists', () => {
|
||||
const base = document.createElement('base');
|
||||
base.setAttribute('href', '/signoz/');
|
||||
document.head.prepend(base);
|
||||
expect(readBasePath()).toBe('/signoz/');
|
||||
});
|
||||
|
||||
it('returns "/" when <base> tag exists but has no href attribute', () => {
|
||||
const base = document.createElement('base');
|
||||
document.head.prepend(base);
|
||||
expect(readBasePath()).toBe('/');
|
||||
});
|
||||
|
||||
it('returns "/" when <base href="/"> exists (root deployment)', () => {
|
||||
const base = document.createElement('base');
|
||||
base.setAttribute('href', '/');
|
||||
document.head.prepend(base);
|
||||
expect(readBasePath()).toBe('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBasePath (module-init snapshot)', () => {
|
||||
it('returns the value captured when the module was first loaded', () => {
|
||||
// In Jest, no <base> tag is present when the module loads, so the
|
||||
// snapshot is '/'. This test documents the singleton contract.
|
||||
expect(getBasePath()).toBe('/');
|
||||
});
|
||||
});
|
||||
21
frontend/src/lib/basePath.ts
Normal file
21
frontend/src/lib/basePath.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Reads the <base href> injected by the backend at serve time (or by
|
||||
* createHtmlPlugin in dev). Returns '/' if no <base> tag is present,
|
||||
* which matches root-path deployments with no backend injection.
|
||||
*
|
||||
* Called once at module init — result is stable for the page lifetime.
|
||||
* Exported for testing; consumers should use getBasePath().
|
||||
*/
|
||||
export function readBasePath(): string {
|
||||
if (typeof document === 'undefined') {
|
||||
return '/';
|
||||
}
|
||||
return document.querySelector('base')?.getAttribute('href') ?? '/';
|
||||
}
|
||||
|
||||
/** @internal Use getBasePath() in application code. */
|
||||
export const basePath: string = readBasePath();
|
||||
|
||||
export function getBasePath(): string {
|
||||
return basePath;
|
||||
}
|
||||
@@ -1,3 +1,9 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
|
||||
export default createBrowserHistory();
|
||||
import { getBasePath } from './basePath';
|
||||
|
||||
// Strip the trailing slash that <base href> includes ('/signoz/' → '/signoz')
|
||||
// because createBrowserHistory expects a basename without trailing slash.
|
||||
const basename = getBasePath().replace(/\/$/, '') || undefined;
|
||||
|
||||
export default createBrowserHistory({ basename });
|
||||
|
||||
@@ -191,6 +191,17 @@ export const handlers = [
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.post('http://localhost/api/v1/changePassword', (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(403),
|
||||
ctx.json({
|
||||
status: 'error',
|
||||
errorType: 'forbidden',
|
||||
error: 'invalid credentials',
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
rest.get(
|
||||
'http://localhost/api/v3/autocomplete/aggregate_attributes',
|
||||
(req, res, ctx) =>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useCallback } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import history from 'lib/history';
|
||||
import { Home, LifeBuoy } from 'lucide-react';
|
||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||
|
||||
@@ -11,8 +12,7 @@ import './ErrorBoundaryFallback.styles.scss';
|
||||
|
||||
function ErrorBoundaryFallback(): JSX.Element {
|
||||
const handleReload = (): void => {
|
||||
// Go to home page
|
||||
window.location.href = ROUTES.HOME;
|
||||
history.push(ROUTES.HOME);
|
||||
};
|
||||
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import history from 'lib/history';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
export const handleContactSupport = (isCloudUser: boolean): void => {
|
||||
if (isCloudUser) {
|
||||
history.push('/support');
|
||||
} else {
|
||||
window.open('https://signoz.io/slack', '_blank');
|
||||
openExternalLink('https://signoz.io/slack');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import {
|
||||
convertToMilliseconds,
|
||||
@@ -93,7 +94,7 @@ export function getColumns(
|
||||
key={item}
|
||||
className="traceid-text"
|
||||
onClick={(): void => {
|
||||
window.open(`${ROUTES.TRACE}/${item}`, '_blank');
|
||||
openInNewTab(`${ROUTES.TRACE}/${item}`);
|
||||
logEvent(`MQ Kafka: Drop Rate - traceid navigation`, {
|
||||
item,
|
||||
});
|
||||
@@ -123,7 +124,7 @@ export function getColumns(
|
||||
onClick={(e): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.open(`/services/${encodeURIComponent(text)}`, '_blank');
|
||||
openInNewTab(`/services/${encodeURIComponent(text)}`);
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
|
||||
@@ -10,7 +10,7 @@ import ROUTES from 'constants/routes';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { openExternalLink, openInNewTab } from 'utils/navigation';
|
||||
|
||||
import {
|
||||
KAFKA_SETUP_DOC_LINK,
|
||||
@@ -59,7 +59,7 @@ function MessagingQueues(): JSX.Element {
|
||||
history.push(link);
|
||||
}
|
||||
} else {
|
||||
window.open(KAFKA_SETUP_DOC_LINK, '_blank');
|
||||
openExternalLink(KAFKA_SETUP_DOC_LINK);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||
import APIError from 'types/api/error';
|
||||
import { openExternalLink } from 'utils/navigation';
|
||||
|
||||
import './Support.styles.scss';
|
||||
|
||||
@@ -92,7 +93,7 @@ export default function Support(): JSX.Element {
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const handleChannelWithRedirects = (url: string): void => {
|
||||
window.open(url, '_blank');
|
||||
openExternalLink(url);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
12
frontend/src/types/api/user/changeMyPassword.ts
Normal file
12
frontend/src/types/api/user/changeMyPassword.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { User } from 'types/reducer/app';
|
||||
|
||||
export interface Props {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
userId: User['userId'];
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
status: string;
|
||||
data: string;
|
||||
}
|
||||
15
frontend/src/types/api/user/getResetPasswordToken.ts
Normal file
15
frontend/src/types/api/user/getResetPasswordToken.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { User } from 'types/reducer/app';
|
||||
|
||||
export interface Props {
|
||||
userId: User['userId'];
|
||||
}
|
||||
|
||||
export interface GetResetPasswordToken {
|
||||
token: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
data: GetResetPasswordToken;
|
||||
status: string;
|
||||
}
|
||||
@@ -1,80 +1,140 @@
|
||||
import { isModifierKeyPressed } from '../app';
|
||||
import { openInNewTab } from '../navigation';
|
||||
import { openExternalLink, openInNewTab } from '../navigation';
|
||||
|
||||
describe('navigation utilities', () => {
|
||||
const originalWindowOpen = window.open;
|
||||
|
||||
beforeEach(() => {
|
||||
window.open = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.open = originalWindowOpen;
|
||||
});
|
||||
|
||||
describe('isModifierKeyPressed', () => {
|
||||
const createMouseEvent = (overrides: Partial<MouseEvent> = {}): MouseEvent =>
|
||||
// Mock history before importing navigation so history.createHref is controlled.
|
||||
jest.mock('lib/history', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
createHref: jest.fn(
|
||||
({
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
button: 0,
|
||||
...overrides,
|
||||
} as MouseEvent);
|
||||
pathname,
|
||||
search,
|
||||
hash,
|
||||
}: {
|
||||
pathname: string;
|
||||
search?: string;
|
||||
hash?: string;
|
||||
}) => `/signoz${pathname}${search || ''}${hash || ''}`,
|
||||
),
|
||||
},
|
||||
}));
|
||||
|
||||
it('returns true when metaKey is pressed (Cmd on Mac)', () => {
|
||||
const event = createMouseEvent({ metaKey: true });
|
||||
expect(isModifierKeyPressed(event)).toBe(true);
|
||||
});
|
||||
const mockWindowOpen = jest.fn();
|
||||
Object.defineProperty(window, 'open', {
|
||||
value: mockWindowOpen,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
it('returns true when ctrlKey is pressed (Ctrl on Windows/Linux)', () => {
|
||||
const event = createMouseEvent({ ctrlKey: true });
|
||||
expect(isModifierKeyPressed(event)).toBe(true);
|
||||
});
|
||||
describe('openInNewTab', () => {
|
||||
beforeEach(() => mockWindowOpen.mockClear());
|
||||
|
||||
it('returns true when both metaKey and ctrlKey are pressed', () => {
|
||||
const event = createMouseEvent({ metaKey: true, ctrlKey: true });
|
||||
expect(isModifierKeyPressed(event)).toBe(true);
|
||||
});
|
||||
// Previously: window.open(path, '_blank') — no base path prepended.
|
||||
// Now: internal paths go through history.createHref so sub-path
|
||||
// deployments (e.g. /signoz/) are handled correctly.
|
||||
|
||||
it('returns false when neither modifier key is pressed', () => {
|
||||
const event = createMouseEvent();
|
||||
expect(isModifierKeyPressed(event)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when only shiftKey or altKey are pressed', () => {
|
||||
const event = createMouseEvent({
|
||||
shiftKey: true,
|
||||
altKey: true,
|
||||
} as Partial<MouseEvent>);
|
||||
expect(isModifierKeyPressed(event)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when middle mouse button is used', () => {
|
||||
const event = createMouseEvent({ button: 1 });
|
||||
expect(isModifierKeyPressed(event)).toBe(true);
|
||||
});
|
||||
it('prepends base path to internal path via history.createHref', () => {
|
||||
openInNewTab('/dashboard');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith('/signoz/dashboard', '_blank');
|
||||
});
|
||||
|
||||
describe('openInNewTab', () => {
|
||||
it('calls window.open with the given path and _blank target', () => {
|
||||
openInNewTab('/dashboard');
|
||||
expect(window.open).toHaveBeenCalledWith('/dashboard', '_blank');
|
||||
});
|
||||
it('preserves query string when prepending base path', () => {
|
||||
openInNewTab('/alerts?tab=AlertRules&relativeTime=30m');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'/signoz/alerts?tab=AlertRules&relativeTime=30m',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
|
||||
it('handles full URLs', () => {
|
||||
openInNewTab('https://example.com/page');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://example.com/page',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
it('preserves hash when prepending base path', () => {
|
||||
openInNewTab('/dashboard/123#panel-5');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'/signoz/dashboard/123#panel-5',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
|
||||
it('handles paths with query strings', () => {
|
||||
openInNewTab('/alerts?tab=AlertRules&relativeTime=30m');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'/alerts?tab=AlertRules&relativeTime=30m',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
// External URLs bypass createHref and are passed through as-is.
|
||||
it('passes http URL directly to window.open without base path', () => {
|
||||
openInNewTab('https://example.com/page');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'https://example.com/page',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
|
||||
it('passes protocol-relative URL directly to window.open without base path', () => {
|
||||
openInNewTab('//cdn.example.com/asset.js');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'//cdn.example.com/asset.js',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isModifierKeyPressed', () => {
|
||||
const createMouseEvent = (overrides: Partial<MouseEvent> = {}): MouseEvent =>
|
||||
({
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
button: 0,
|
||||
...overrides,
|
||||
} as MouseEvent);
|
||||
|
||||
it('returns true when metaKey is pressed (Cmd on Mac)', () => {
|
||||
const event = createMouseEvent({ metaKey: true });
|
||||
expect(isModifierKeyPressed(event)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when ctrlKey is pressed (Ctrl on Windows/Linux)', () => {
|
||||
const event = createMouseEvent({ ctrlKey: true });
|
||||
expect(isModifierKeyPressed(event)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when both metaKey and ctrlKey are pressed', () => {
|
||||
const event = createMouseEvent({ metaKey: true, ctrlKey: true });
|
||||
expect(isModifierKeyPressed(event)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when neither modifier key is pressed', () => {
|
||||
const event = createMouseEvent();
|
||||
expect(isModifierKeyPressed(event)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when only shiftKey or altKey are pressed', () => {
|
||||
const event = createMouseEvent({
|
||||
shiftKey: true,
|
||||
altKey: true,
|
||||
} as Partial<MouseEvent>);
|
||||
expect(isModifierKeyPressed(event)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when middle mouse button is used', () => {
|
||||
const event = createMouseEvent({ button: 1 });
|
||||
expect(isModifierKeyPressed(event)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('openExternalLink', () => {
|
||||
beforeEach(() => mockWindowOpen.mockClear());
|
||||
|
||||
// openExternalLink is new — replaces ad-hoc window.open calls for external
|
||||
// URLs and always adds noopener,noreferrer for security.
|
||||
|
||||
it('opens external URL with noopener,noreferrer', () => {
|
||||
openExternalLink('https://signoz.io/slack');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'https://signoz.io/slack',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
});
|
||||
|
||||
it('opens protocol-relative external URL with noopener,noreferrer', () => {
|
||||
openExternalLink('//docs.signoz.io/setup');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'//docs.signoz.io/setup',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
95
frontend/src/utils/navigation.test.ts
Normal file
95
frontend/src/utils/navigation.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
// Mock history before importing navigation, so history.createHref is controlled.
|
||||
jest.mock('lib/history', () => ({
|
||||
__esModule: true,
|
||||
default: {
|
||||
createHref: jest.fn(
|
||||
({
|
||||
pathname,
|
||||
search,
|
||||
hash,
|
||||
}: {
|
||||
pathname: string;
|
||||
search?: string;
|
||||
hash?: string;
|
||||
}) => `/signoz${pathname}${search || ''}${hash || ''}`,
|
||||
),
|
||||
},
|
||||
}));
|
||||
|
||||
import { openExternalLink, openInNewTab } from './navigation';
|
||||
|
||||
const mockWindowOpen = jest.fn();
|
||||
Object.defineProperty(window, 'open', {
|
||||
value: mockWindowOpen,
|
||||
writable: true,
|
||||
});
|
||||
|
||||
describe('openInNewTab', () => {
|
||||
beforeEach(() => mockWindowOpen.mockClear());
|
||||
|
||||
it('opens external http URL as-is without prepending base path', () => {
|
||||
openInNewTab('https://signoz.io/docs');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'https://signoz.io/docs',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
|
||||
it('opens external protocol-relative URL as-is', () => {
|
||||
openInNewTab('//cdn.example.com/asset.js');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'//cdn.example.com/asset.js',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
|
||||
it('prepends base path to internal path via history.createHref', () => {
|
||||
openInNewTab('/traces');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith('/signoz/traces', '_blank');
|
||||
});
|
||||
|
||||
it('preserves query string when prepending base path', () => {
|
||||
openInNewTab('/traces?service=frontend&env=prod');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'/signoz/traces?service=frontend&env=prod',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves hash when prepending base path', () => {
|
||||
openInNewTab('/dashboard/123#panel-5');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'/signoz/dashboard/123#panel-5',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
|
||||
it('handles path without leading slash by resolving against origin', () => {
|
||||
openInNewTab('traces');
|
||||
// new URL('traces', window.location.origin) resolves to origin + '/traces'
|
||||
// history.createHref is called with pathname: '/traces'
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith('/signoz/traces', '_blank');
|
||||
});
|
||||
});
|
||||
|
||||
describe('openExternalLink', () => {
|
||||
beforeEach(() => mockWindowOpen.mockClear());
|
||||
|
||||
it('opens external URL with noopener,noreferrer', () => {
|
||||
openExternalLink('https://signoz.io/slack');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'https://signoz.io/slack',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
});
|
||||
|
||||
it('opens protocol-relative external URL', () => {
|
||||
openExternalLink('//docs.signoz.io/setup');
|
||||
expect(mockWindowOpen).toHaveBeenCalledWith(
|
||||
'//docs.signoz.io/setup',
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,37 @@
|
||||
import history from 'lib/history';
|
||||
|
||||
/**
|
||||
* Opens the given path in a new browser tab.
|
||||
* Opens an internal SigNoz path in a new tab.
|
||||
* Automatically prepends the runtime base path via history.createHref so
|
||||
* sub-path deployments (e.g. /signoz/) work correctly.
|
||||
*
|
||||
* For external URLs (http/https), use openExternalLink() instead.
|
||||
*/
|
||||
export const openInNewTab = (path: string): void => {
|
||||
window.open(path, '_blank');
|
||||
if (path.startsWith('http') || path.startsWith('//')) {
|
||||
window.open(path, '_blank');
|
||||
return;
|
||||
}
|
||||
// Parse the path so query params and hash are passed to createHref
|
||||
// separately — passing a full URL string as `pathname` embeds the search
|
||||
// string inside the path segment, which is incorrect.
|
||||
const parsed = new URL(path, window.location.origin);
|
||||
window.open(
|
||||
history.createHref({
|
||||
pathname: parsed.pathname,
|
||||
search: parsed.search,
|
||||
hash: parsed.hash,
|
||||
}),
|
||||
'_blank',
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens an external URL in a new tab with noopener,noreferrer.
|
||||
* Use this for links to external sites (docs, Slack, marketing pages).
|
||||
*
|
||||
* For internal SigNoz routes, use openInNewTab() instead.
|
||||
*/
|
||||
export const openExternalLink = (url: string): void => {
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ export default defineConfig(
|
||||
data: {
|
||||
PYLON_APP_ID: env.VITE_PYLON_APP_ID || '',
|
||||
APPCUES_APP_ID: env.VITE_APPCUES_APP_ID || '',
|
||||
BASE_PATH: env.VITE_BASE_PATH || '/', // ← REMOVE when BE injection is live
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -81,6 +82,7 @@ export default defineConfig(
|
||||
|
||||
return {
|
||||
plugins,
|
||||
base: './',
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, './src'),
|
||||
|
||||
6
go.mod
6
go.mod
@@ -15,7 +15,6 @@ require (
|
||||
github.com/coreos/go-oidc/v3 v3.17.0
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/emersion/go-smtp v0.24.0
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/go-co-op/gocron v1.30.1
|
||||
github.com/go-openapi/runtime v0.29.2
|
||||
@@ -29,7 +28,7 @@ require (
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||
github.com/huandu/go-sqlbuilder v1.35.0
|
||||
github.com/jackc/pgx/v5 v5.9.0
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12
|
||||
github.com/knadh/koanf v1.5.0
|
||||
github.com/knadh/koanf/v2 v2.3.2
|
||||
@@ -112,7 +111,6 @@ require (
|
||||
github.com/bytedance/sonic v1.14.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/emersion/go-sasl v0.0.0-20241020182733-b788ff22d5a6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/conv v0.25.4 // indirect
|
||||
@@ -166,7 +164,7 @@ require (
|
||||
github.com/ClickHouse/ch-go v0.67.0 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/Yiling-J/theine-go v0.6.2 // indirect
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b
|
||||
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -672,8 +672,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.0 h1:T/dI+2TvmI2H8s/KH1/lXIbz1CUFk3gn5oTjr0/mBsE=
|
||||
github.com/jackc/pgx/v5 v5.9.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jessevdk/go-flags v1.6.1 h1:Cvu5U8UGrLay1rZfv/zP7iLpSHGUZ/Ou68T0iX1bBK4=
|
||||
|
||||
@@ -1,431 +0,0 @@
|
||||
// Copyright (c) 2026 SigNoz, Inc.
|
||||
// Copyright 2019 Prometheus Team
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package email
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"mime/quotedprintable"
|
||||
"net"
|
||||
"net/mail"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
commoncfg "github.com/prometheus/common/config"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/notify"
|
||||
"github.com/prometheus/alertmanager/template"
|
||||
"github.com/prometheus/alertmanager/types"
|
||||
)
|
||||
|
||||
const (
|
||||
Integration = "email"
|
||||
)
|
||||
|
||||
// Email implements a Notifier for email notifications.
|
||||
type Email struct {
|
||||
conf *config.EmailConfig
|
||||
tmpl *template.Template
|
||||
logger *slog.Logger
|
||||
hostname string
|
||||
}
|
||||
|
||||
var errNoAuthUsernameConfigured = errors.NewInternalf(errors.CodeInternal, "no auth username configured")
|
||||
|
||||
// New returns a new Email notifier.
|
||||
func New(c *config.EmailConfig, t *template.Template, l *slog.Logger) *Email {
|
||||
if _, ok := c.Headers["Subject"]; !ok {
|
||||
c.Headers["Subject"] = config.DefaultEmailSubject
|
||||
}
|
||||
if _, ok := c.Headers["To"]; !ok {
|
||||
c.Headers["To"] = c.To
|
||||
}
|
||||
if _, ok := c.Headers["From"]; !ok {
|
||||
c.Headers["From"] = c.From
|
||||
}
|
||||
|
||||
h, err := os.Hostname()
|
||||
// If we can't get the hostname, we'll use localhost
|
||||
if err != nil {
|
||||
h = "localhost.localdomain"
|
||||
}
|
||||
return &Email{conf: c, tmpl: t, logger: l, hostname: h}
|
||||
}
|
||||
|
||||
// auth resolves a string of authentication mechanisms.
|
||||
func (n *Email) auth(mechs string) (smtp.Auth, error) {
|
||||
username := n.conf.AuthUsername
|
||||
|
||||
// If no username is set, return custom error which can be ignored if needed.
|
||||
if strings.TrimSpace(username) == "" {
|
||||
return nil, errNoAuthUsernameConfigured
|
||||
}
|
||||
|
||||
var errs error
|
||||
for mech := range strings.SplitSeq(mechs, " ") {
|
||||
switch mech {
|
||||
case "CRAM-MD5":
|
||||
secret, secretErr := n.getAuthSecret()
|
||||
if secretErr != nil {
|
||||
errs = errors.Join(errs, secretErr)
|
||||
continue
|
||||
}
|
||||
if secret == "" {
|
||||
errs = errors.Join(errs, errors.NewInternalf(errors.CodeInternal, "missing secret for CRAM-MD5 auth mechanism"))
|
||||
continue
|
||||
}
|
||||
return smtp.CRAMMD5Auth(username, secret), nil
|
||||
|
||||
case "PLAIN":
|
||||
password, passwordErr := n.getPassword()
|
||||
if passwordErr != nil {
|
||||
errs = errors.Join(errs, passwordErr)
|
||||
continue
|
||||
}
|
||||
if password == "" {
|
||||
errs = errors.Join(errs, errors.NewInternalf(errors.CodeInternal, "missing password for PLAIN auth mechanism"))
|
||||
continue
|
||||
}
|
||||
return smtp.PlainAuth(n.conf.AuthIdentity, username, password, n.conf.Smarthost.Host), nil
|
||||
case "LOGIN":
|
||||
password, passwordErr := n.getPassword()
|
||||
if passwordErr != nil {
|
||||
errs = errors.Join(errs, passwordErr)
|
||||
continue
|
||||
}
|
||||
if password == "" {
|
||||
errs = errors.Join(errs, errors.NewInternalf(errors.CodeInternal, "missing password for LOGIN auth mechanism"))
|
||||
continue
|
||||
}
|
||||
return LoginAuth(username, password), nil
|
||||
default:
|
||||
errs = errors.Join(errs, errors.NewInternalf(errors.CodeUnsupported, "unknown auth mechanism: %s", mech))
|
||||
}
|
||||
}
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
// Notify implements the Notifier interface.
|
||||
func (n *Email) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
|
||||
var (
|
||||
c *smtp.Client
|
||||
conn net.Conn
|
||||
err error
|
||||
success = false
|
||||
)
|
||||
// Determine whether to use Implicit TLS
|
||||
var useImplicitTLS bool
|
||||
if n.conf.ForceImplicitTLS != nil {
|
||||
useImplicitTLS = *n.conf.ForceImplicitTLS
|
||||
} else {
|
||||
// Default logic: port 465 uses implicit TLS (backward compatibility)
|
||||
useImplicitTLS = n.conf.Smarthost.Port == "465"
|
||||
}
|
||||
|
||||
if useImplicitTLS {
|
||||
tlsConfig, err := commoncfg.NewTLSConfig(n.conf.TLSConfig)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "parse TLS configuration")
|
||||
}
|
||||
if tlsConfig.ServerName == "" {
|
||||
tlsConfig.ServerName = n.conf.Smarthost.Host
|
||||
}
|
||||
|
||||
conn, err = tls.Dial("tcp", n.conf.Smarthost.String(), tlsConfig)
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "establish TLS connection to server")
|
||||
}
|
||||
} else {
|
||||
var (
|
||||
d = net.Dialer{}
|
||||
err error
|
||||
)
|
||||
conn, err = d.DialContext(ctx, "tcp", n.conf.Smarthost.String())
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "establish connection to server")
|
||||
}
|
||||
}
|
||||
c, err = smtp.NewClient(conn, n.conf.Smarthost.Host)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "create SMTP client")
|
||||
}
|
||||
defer func() {
|
||||
// Try to clean up after ourselves but don't log anything if something has failed.
|
||||
if err := c.Quit(); success && err != nil {
|
||||
n.logger.WarnContext(ctx, "failed to close SMTP connection", slog.Any("err", err))
|
||||
}
|
||||
}()
|
||||
|
||||
if n.conf.Hello != "" {
|
||||
err = c.Hello(n.conf.Hello)
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send EHLO command")
|
||||
}
|
||||
}
|
||||
|
||||
// Global Config guarantees RequireTLS is not nil.
|
||||
if *n.conf.RequireTLS && !useImplicitTLS {
|
||||
if ok, _ := c.Extension("STARTTLS"); !ok {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "'require_tls' is true (default) but %q does not advertise the STARTTLS extension", n.conf.Smarthost)
|
||||
}
|
||||
|
||||
tlsConf, err := commoncfg.NewTLSConfig(n.conf.TLSConfig)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "parse TLS configuration")
|
||||
}
|
||||
if tlsConf.ServerName == "" {
|
||||
tlsConf.ServerName = n.conf.Smarthost.Host
|
||||
}
|
||||
|
||||
if err := c.StartTLS(tlsConf); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send STARTTLS command")
|
||||
}
|
||||
}
|
||||
|
||||
if ok, mech := c.Extension("AUTH"); ok {
|
||||
auth, err := n.auth(mech)
|
||||
if err != nil && err != errNoAuthUsernameConfigured {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "find auth mechanism")
|
||||
} else if err == errNoAuthUsernameConfigured {
|
||||
n.logger.DebugContext(ctx, "no auth username configured. Attempting to send email without authenticating")
|
||||
}
|
||||
if auth != nil {
|
||||
if err := c.Auth(auth); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "%T auth", auth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
tmplErr error
|
||||
data = notify.GetTemplateData(ctx, n.tmpl, as, n.logger)
|
||||
tmpl = notify.TmplText(n.tmpl, data, &tmplErr)
|
||||
)
|
||||
from := tmpl(n.conf.From)
|
||||
if tmplErr != nil {
|
||||
return false, errors.WrapInternalf(tmplErr, errors.CodeInternal, "execute 'from' template")
|
||||
}
|
||||
to := tmpl(n.conf.To)
|
||||
if tmplErr != nil {
|
||||
return false, errors.WrapInternalf(tmplErr, errors.CodeInternal, "execute 'to' template")
|
||||
}
|
||||
|
||||
addrs, err := mail.ParseAddressList(from)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "parse 'from' addresses")
|
||||
}
|
||||
if len(addrs) != 1 {
|
||||
return false, errors.NewInternalf(errors.CodeInternal, "must be exactly one 'from' address (got: %d)", len(addrs))
|
||||
}
|
||||
if err = c.Mail(addrs[0].Address); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send MAIL command")
|
||||
}
|
||||
addrs, err = mail.ParseAddressList(to)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "parse 'to' addresses")
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if err = c.Rcpt(addr.Address); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send RCPT command")
|
||||
}
|
||||
}
|
||||
|
||||
// Send the email headers and body.
|
||||
message, err := c.Data()
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "send DATA command")
|
||||
}
|
||||
closeOnce := sync.OnceValue(func() error {
|
||||
return message.Close()
|
||||
})
|
||||
// Close the message when this method exits in order to not leak resources. Even though we're calling this explicitly
|
||||
// further down, the method may exit before then.
|
||||
defer func() {
|
||||
// If we try close an already-closed writer, it'll send a subsequent request to the server which is invalid.
|
||||
_ = closeOnce()
|
||||
}()
|
||||
|
||||
buffer := &bytes.Buffer{}
|
||||
for header, t := range n.conf.Headers {
|
||||
value, err := n.tmpl.ExecuteTextString(t, data)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "execute %q header template", header)
|
||||
}
|
||||
fmt.Fprintf(buffer, "%s: %s\r\n", header, mime.QEncoding.Encode("utf-8", value))
|
||||
}
|
||||
|
||||
if _, ok := n.conf.Headers["Message-Id"]; !ok {
|
||||
fmt.Fprintf(buffer, "Message-Id: %s\r\n", fmt.Sprintf("<%d.%d@%s>", time.Now().UnixNano(), rand.Uint64(), n.hostname))
|
||||
}
|
||||
|
||||
if n.conf.Threading.Enabled {
|
||||
key, err := notify.ExtractGroupKey(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// Add threading headers. All notifications for the same alert group
|
||||
// (identified by key hash) are threaded together.
|
||||
threadBy := ""
|
||||
if n.conf.Threading.ThreadByDate != "none" {
|
||||
// ThreadByDate is 'daily':
|
||||
// Use current date so all mails for this alert today thread together.
|
||||
threadBy = time.Now().Format("2006-01-02")
|
||||
}
|
||||
keyHash := key.Hash()
|
||||
if len(keyHash) > 16 {
|
||||
keyHash = keyHash[:16]
|
||||
}
|
||||
// The thread root ID is a Message-ID that doesn't correspond to
|
||||
// any actual email. Email clients following the (commonly used) JWZ
|
||||
// algorithm will create a dummy container to group these messages.
|
||||
threadRootID := fmt.Sprintf("<alert-%s-%s@alertmanager>", keyHash, threadBy)
|
||||
fmt.Fprintf(buffer, "References: %s\r\n", threadRootID)
|
||||
fmt.Fprintf(buffer, "In-Reply-To: %s\r\n", threadRootID)
|
||||
}
|
||||
|
||||
multipartBuffer := &bytes.Buffer{}
|
||||
multipartWriter := multipart.NewWriter(multipartBuffer)
|
||||
|
||||
fmt.Fprintf(buffer, "Date: %s\r\n", time.Now().Format(time.RFC1123Z))
|
||||
fmt.Fprintf(buffer, "Content-Type: multipart/alternative; boundary=%s\r\n", multipartWriter.Boundary())
|
||||
fmt.Fprintf(buffer, "MIME-Version: 1.0\r\n\r\n")
|
||||
|
||||
// TODO: Add some useful headers here, such as URL of the alertmanager
|
||||
// and active/resolved.
|
||||
_, err = message.Write(buffer.Bytes())
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "write headers")
|
||||
}
|
||||
|
||||
if len(n.conf.Text) > 0 {
|
||||
// Text template
|
||||
w, err := multipartWriter.CreatePart(textproto.MIMEHeader{
|
||||
"Content-Transfer-Encoding": {"quoted-printable"},
|
||||
"Content-Type": {"text/plain; charset=UTF-8"},
|
||||
})
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "create part for text template")
|
||||
}
|
||||
body, err := n.tmpl.ExecuteTextString(n.conf.Text, data)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "execute text template")
|
||||
}
|
||||
qw := quotedprintable.NewWriter(w)
|
||||
_, err = qw.Write([]byte(body))
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "write text part")
|
||||
}
|
||||
err = qw.Close()
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "close text part")
|
||||
}
|
||||
}
|
||||
|
||||
if len(n.conf.HTML) > 0 {
|
||||
// Html template
|
||||
// Preferred alternative placed last per section 5.1.4 of RFC 2046
|
||||
// https://www.ietf.org/rfc/rfc2046.txt
|
||||
w, err := multipartWriter.CreatePart(textproto.MIMEHeader{
|
||||
"Content-Transfer-Encoding": {"quoted-printable"},
|
||||
"Content-Type": {"text/html; charset=UTF-8"},
|
||||
})
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "create part for html template")
|
||||
}
|
||||
body, err := n.tmpl.ExecuteHTMLString(n.conf.HTML, data)
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "execute html template")
|
||||
}
|
||||
qw := quotedprintable.NewWriter(w)
|
||||
_, err = qw.Write([]byte(body))
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "write HTML part")
|
||||
}
|
||||
err = qw.Close()
|
||||
if err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "close HTML part")
|
||||
}
|
||||
}
|
||||
|
||||
err = multipartWriter.Close()
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "close multipartWriter")
|
||||
}
|
||||
|
||||
_, err = message.Write(multipartBuffer.Bytes())
|
||||
if err != nil {
|
||||
return false, errors.WrapInternalf(err, errors.CodeInternal, "write body buffer")
|
||||
}
|
||||
|
||||
// Complete the message and await response.
|
||||
if err = closeOnce(); err != nil {
|
||||
return true, errors.WrapInternalf(err, errors.CodeInternal, "delivery failure")
|
||||
}
|
||||
|
||||
success = true
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type loginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
|
||||
func LoginAuth(username, password string) smtp.Auth {
|
||||
return &loginAuth{username, password}
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
return "LOGIN", []byte{}, nil
|
||||
}
|
||||
|
||||
// Used for AUTH LOGIN. (Maybe password should be encrypted).
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
switch strings.ToLower(string(fromServer)) {
|
||||
case "username:":
|
||||
return []byte(a.username), nil
|
||||
case "password:":
|
||||
return []byte(a.password), nil
|
||||
default:
|
||||
return nil, errors.NewInternalf(errors.CodeInternal, "unexpected server challenge")
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (n *Email) getPassword() (string, error) {
|
||||
if len(n.conf.AuthPasswordFile) > 0 {
|
||||
content, err := os.ReadFile(n.conf.AuthPasswordFile)
|
||||
if err != nil {
|
||||
return "", errors.NewInternalf(errors.CodeInternal, "could not read %s: %v", n.conf.AuthPasswordFile, err)
|
||||
}
|
||||
return strings.TrimSpace(string(content)), nil
|
||||
}
|
||||
return string(n.conf.AuthPassword), nil
|
||||
}
|
||||
|
||||
func (n *Email) getAuthSecret() (string, error) {
|
||||
if len(n.conf.AuthSecretFile) > 0 {
|
||||
content, err := os.ReadFile(n.conf.AuthSecretFile)
|
||||
if err != nil {
|
||||
return "", errors.NewInternalf(errors.CodeInternal, "could not read %s: %v", n.conf.AuthSecretFile, err)
|
||||
}
|
||||
return string(content), nil
|
||||
}
|
||||
return string(n.conf.AuthSecret), nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,3 @@
|
||||
// Copyright (c) 2026 SigNoz, Inc.
|
||||
// Copyright 2019 Prometheus Team
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package msteamsv2
|
||||
|
||||
import (
|
||||
@@ -31,10 +27,6 @@ const (
|
||||
colorGrey = "Warning"
|
||||
)
|
||||
|
||||
const (
|
||||
Integration = "msteamsv2"
|
||||
)
|
||||
|
||||
type Notifier struct {
|
||||
conf *config.MSTeamsV2Config
|
||||
titleLink string
|
||||
@@ -95,7 +87,7 @@ type teamsMessage struct {
|
||||
|
||||
// New returns a new notifier that uses the Microsoft Teams Power Platform connector.
|
||||
func New(c *config.MSTeamsV2Config, t *template.Template, titleLink string, l *slog.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
|
||||
client, err := notify.NewClientWithTracing(*c.HTTPConfig, Integration, httpOpts...)
|
||||
client, err := commoncfg.NewClientFromConfig(*c.HTTPConfig, "msteamsv2", httpOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// Copyright (c) 2026 SigNoz, Inc.
|
||||
// Copyright 2019 Prometheus Team
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package msteamsv2
|
||||
|
||||
import (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user