mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-21 11:20:28 +01:00
Compare commits
26 Commits
platform-p
...
base-path-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d7d489fb2 | ||
|
|
61ee30b5f0 | ||
|
|
71fd5910a4 | ||
|
|
06e521ad97 | ||
|
|
a9dcad863b | ||
|
|
8bbc5092fa | ||
|
|
1969dc536f | ||
|
|
6e4d6a9511 | ||
|
|
964bd79415 | ||
|
|
a93b26f25e | ||
|
|
ba0a5a1056 | ||
|
|
e9d5012758 | ||
|
|
34a08adde7 | ||
|
|
44cb8f5927 | ||
|
|
c88c7bac0f | ||
|
|
72db77b068 | ||
|
|
4f84f07494 | ||
|
|
a9e09ee349 | ||
|
|
0a9bb6ba0b | ||
|
|
6664a0fae3 | ||
|
|
040dcb9c9b | ||
|
|
4a39453826 | ||
|
|
ac4db09ec6 | ||
|
|
a691e6a775 | ||
|
|
d270a3807b | ||
|
|
c2b553d26c |
@@ -66,6 +66,8 @@ module.exports = {
|
||||
rules: {
|
||||
// Asset migration — base-path safety
|
||||
'rulesdir/no-unsupported-asset-pattern': 'error',
|
||||
// Base-path safety — window.open and origin-concat patterns
|
||||
'rulesdir/no-raw-absolute-path': 'error',
|
||||
|
||||
// Code quality rules
|
||||
'prefer-const': 'error', // Enforces const for variables never reassigned
|
||||
@@ -213,6 +215,31 @@ module.exports = {
|
||||
message:
|
||||
'Avoid calling .getState() directly. Export a standalone action from the store instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"MemberExpression[object.name='window'][property.name='localStorage']",
|
||||
message:
|
||||
'Use getLocalStorageKey/setLocalStorageKey/removeLocalStorageKey from api/browser/localstorage instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"MemberExpression[object.name='window'][property.name='sessionStorage']",
|
||||
message:
|
||||
'Use getSessionStorageApi/setSessionStorageApi/removeSessionStorageApi from api/browser/sessionstorage instead.',
|
||||
},
|
||||
],
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{
|
||||
name: 'localStorage',
|
||||
message:
|
||||
'Use getLocalStorageKey/setLocalStorageKey/removeLocalStorageKey from api/browser/localstorage instead.',
|
||||
},
|
||||
{
|
||||
name: 'sessionStorage',
|
||||
message:
|
||||
'Use getSessionStorageApi/setSessionStorageApi/removeSessionStorageApi from api/browser/sessionstorage instead.',
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
@@ -246,6 +273,11 @@ module.exports = {
|
||||
'sonarjs/cognitive-complexity': 'off', // Tests can be complex
|
||||
'sonarjs/no-identical-functions': 'off', // Similar test patterns are OK
|
||||
'sonarjs/no-small-switch': 'off', // Small switches are OK in tests
|
||||
// Test assertions intentionally reference window.location.origin for expected-value checks
|
||||
'rulesdir/no-raw-absolute-path': 'off',
|
||||
// Tests may access storage directly for setup/assertion/spy purposes
|
||||
'no-restricted-globals': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
153
frontend/eslint-rules/no-raw-absolute-path.js
Normal file
153
frontend/eslint-rules/no-raw-absolute-path.js
Normal file
@@ -0,0 +1,153 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* ESLint rule: no-raw-absolute-path
|
||||
*
|
||||
* Catches patterns that break at runtime when the app is served from a
|
||||
* sub-path (e.g. /signoz/):
|
||||
*
|
||||
* 1. window.open(path, '_blank')
|
||||
* → use openInNewTab(path) which calls withBasePath internally
|
||||
*
|
||||
* 2. window.location.origin + path / `${window.location.origin}${path}`
|
||||
* → use getAbsoluteUrl(path)
|
||||
*
|
||||
* 3. frontendBaseUrl: window.location.origin (bare origin usage)
|
||||
* → use getBaseUrl() to include the base path
|
||||
*
|
||||
* 4. window.location.href = path
|
||||
* → use withBasePath(path) or navigate() for internal navigation
|
||||
*
|
||||
* External URLs (first arg starts with "http") are explicitly allowed.
|
||||
*/
|
||||
|
||||
function isOriginAccess(node) {
|
||||
return (
|
||||
node.type === 'MemberExpression' &&
|
||||
!node.computed &&
|
||||
node.property.name === 'origin' &&
|
||||
node.object.type === 'MemberExpression' &&
|
||||
!node.object.computed &&
|
||||
node.object.property.name === 'location' &&
|
||||
node.object.object.type === 'Identifier' &&
|
||||
node.object.object.name === 'window'
|
||||
);
|
||||
}
|
||||
|
||||
function isHrefAccess(node) {
|
||||
return (
|
||||
node.type === 'MemberExpression' &&
|
||||
!node.computed &&
|
||||
node.property.name === 'href' &&
|
||||
node.object.type === 'MemberExpression' &&
|
||||
!node.object.computed &&
|
||||
node.object.property.name === 'location' &&
|
||||
node.object.object.type === 'Identifier' &&
|
||||
node.object.object.name === 'window'
|
||||
);
|
||||
}
|
||||
|
||||
function isExternalUrl(node) {
|
||||
if (node.type === 'Literal' && typeof node.value === 'string') {
|
||||
return node.value.startsWith('http://') || node.value.startsWith('https://');
|
||||
}
|
||||
if (node.type === 'TemplateLiteral' && node.quasis.length > 0) {
|
||||
const raw = node.quasis[0].value.raw;
|
||||
return raw.startsWith('http://') || raw.startsWith('https://');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// window.open(withBasePath(x)) and window.open(getAbsoluteUrl(x)) are already safe.
|
||||
function isSafeHelperCall(node) {
|
||||
return (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee.type === 'Identifier' &&
|
||||
(node.callee.name === 'withBasePath' || node.callee.name === 'getAbsoluteUrl')
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'suggestion',
|
||||
docs: {
|
||||
description:
|
||||
'Disallow raw window.open and origin-concatenation patterns that miss the runtime base path',
|
||||
category: 'Base Path Safety',
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
windowOpen:
|
||||
'Use openInNewTab(path) instead of window.open(path, "_blank") — openInNewTab prepends the base path automatically.',
|
||||
originConcat:
|
||||
'Use getAbsoluteUrl(path) instead of window.location.origin + path — getAbsoluteUrl prepends the base path automatically.',
|
||||
originDirect:
|
||||
'Use getBaseUrl() instead of window.location.origin — getBaseUrl includes the base path.',
|
||||
hrefAssign:
|
||||
'Use withBasePath(path) or navigate() instead of window.location.href = path — ensures the base path is included.',
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
// window.open(path, ...) — allow only external first-arg URLs
|
||||
CallExpression(node) {
|
||||
const { callee, arguments: args } = node;
|
||||
if (
|
||||
callee.type !== 'MemberExpression' ||
|
||||
callee.object.type !== 'Identifier' ||
|
||||
callee.object.name !== 'window' ||
|
||||
callee.property.name !== 'open'
|
||||
)
|
||||
return;
|
||||
if (args.length < 1) return;
|
||||
if (isExternalUrl(args[0])) return;
|
||||
if (isSafeHelperCall(args[0])) return;
|
||||
|
||||
context.report({ node, messageId: 'windowOpen' });
|
||||
},
|
||||
|
||||
// window.location.origin + path
|
||||
BinaryExpression(node) {
|
||||
if (node.operator !== '+') return;
|
||||
if (isOriginAccess(node.left) || isOriginAccess(node.right)) {
|
||||
context.report({ node, messageId: 'originConcat' });
|
||||
}
|
||||
},
|
||||
|
||||
// `${window.location.origin}${path}`
|
||||
TemplateLiteral(node) {
|
||||
if (node.expressions.some(isOriginAccess)) {
|
||||
context.report({ node, messageId: 'originConcat' });
|
||||
}
|
||||
},
|
||||
|
||||
// window.location.origin used directly (not in concatenation)
|
||||
// Catches: frontendBaseUrl: window.location.origin
|
||||
MemberExpression(node) {
|
||||
if (!isOriginAccess(node)) return;
|
||||
|
||||
const parent = node.parent;
|
||||
// Skip if parent is BinaryExpression with + (handled by BinaryExpression visitor)
|
||||
if (parent.type === 'BinaryExpression' && parent.operator === '+') return;
|
||||
// Skip if inside TemplateLiteral (handled by TemplateLiteral visitor)
|
||||
if (parent.type === 'TemplateLiteral') return;
|
||||
|
||||
context.report({ node, messageId: 'originDirect' });
|
||||
},
|
||||
|
||||
// window.location.href = path
|
||||
AssignmentExpression(node) {
|
||||
if (node.operator !== '=') return;
|
||||
if (!isHrefAccess(node.left)) return;
|
||||
|
||||
// Allow external URLs
|
||||
if (isExternalUrl(node.right)) return;
|
||||
// Allow safe helper calls
|
||||
if (isSafeHelperCall(node.right)) return;
|
||||
|
||||
context.report({ node, messageId: 'hrefAssign' });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<base href="[[.BaseHref]]" />
|
||||
<meta
|
||||
http-equiv="Cache-Control"
|
||||
content="no-cache, no-store, must-revalidate, max-age: 0"
|
||||
@@ -39,12 +40,12 @@
|
||||
<meta
|
||||
data-react-helmet="true"
|
||||
property="og:image"
|
||||
content="/images/signoz-hero-image.webp"
|
||||
content="[[.BaseHref]]images/signoz-hero-image.webp"
|
||||
/>
|
||||
<meta
|
||||
data-react-helmet="true"
|
||||
name="twitter:image"
|
||||
content="/images/signoz-hero-image.webp"
|
||||
content="[[.BaseHref]]images/signoz-hero-image.webp"
|
||||
/>
|
||||
<meta
|
||||
data-react-helmet="true"
|
||||
@@ -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">
|
||||
<script>
|
||||
@@ -136,7 +137,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>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { initReactI18next } from 'react-i18next';
|
||||
import i18n from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import Backend from 'i18next-http-backend';
|
||||
import { getBasePath } from 'utils/basePath';
|
||||
|
||||
import cacheBursting from '../../i18n-translations-hash.json';
|
||||
|
||||
@@ -24,7 +25,7 @@ i18n
|
||||
const ns = namespace[0];
|
||||
const pathkey = `/${language}/${ns}`;
|
||||
const hash = cacheBursting[pathkey as keyof typeof cacheBursting] || '';
|
||||
return `/locales/${language}/${namespace}.json?h=${hash}`;
|
||||
return `${getBasePath()}locales/${language}/${namespace}.json?h=${hash}`;
|
||||
},
|
||||
},
|
||||
react: {
|
||||
|
||||
20
frontend/src/api/alerts/create.ts
Normal file
20
frontend/src/api/alerts/create.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/create';
|
||||
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.post('/rules', {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default create;
|
||||
28
frontend/src/api/alerts/createAlertRule.ts
Normal file
28
frontend/src/api/alerts/createAlertRule.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
AlertRuleV2,
|
||||
PostableAlertRuleV2,
|
||||
} from 'types/api/alerts/alertTypesV2';
|
||||
|
||||
export interface CreateAlertRuleResponse {
|
||||
data: AlertRuleV2;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const createAlertRule = async (
|
||||
props: PostableAlertRuleV2,
|
||||
): Promise<SuccessResponse<CreateAlertRuleResponse> | ErrorResponse> => {
|
||||
const response = await axios.post(`/rules`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default createAlertRule;
|
||||
18
frontend/src/api/alerts/delete.ts
Normal file
18
frontend/src/api/alerts/delete.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/delete';
|
||||
|
||||
const deleteAlerts = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.delete(`/rules/${props.id}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data.rules,
|
||||
};
|
||||
};
|
||||
|
||||
export default deleteAlerts;
|
||||
16
frontend/src/api/alerts/get.ts
Normal file
16
frontend/src/api/alerts/get.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/get';
|
||||
|
||||
const get = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.get(`/rules/${props.id}`);
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
};
|
||||
export default get;
|
||||
24
frontend/src/api/alerts/getAll.ts
Normal file
24
frontend/src/api/alerts/getAll.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/alerts/getAll';
|
||||
|
||||
const getAll = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get('/rules');
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data.rules,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getAll;
|
||||
29
frontend/src/api/alerts/getGroup.ts
Normal file
29
frontend/src/api/alerts/getGroup.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { AxiosAlertManagerInstance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/getGroups';
|
||||
|
||||
const getGroups = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const queryParams = convertObjectIntoParams(props);
|
||||
|
||||
const response = await AxiosAlertManagerInstance.get(
|
||||
`/alerts/groups?${queryParams}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getGroups;
|
||||
20
frontend/src/api/alerts/patch.ts
Normal file
20
frontend/src/api/alerts/patch.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/patch';
|
||||
|
||||
const patch = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.patch(`/rules/${props.id}`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default patch;
|
||||
@@ -1,12 +0,0 @@
|
||||
import { patchRuleByID } from 'api/generated/services/rules';
|
||||
import type { RuletypesPostableRuleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
// why: patchRuleByID's generated body type is the full RuletypesPostableRuleDTO
|
||||
// because the backend OpenAPI spec currently advertises PostableRule. The
|
||||
// endpoint itself accepts any subset of fields. Until the backend introduces
|
||||
// PatchableRule, this wrapper localizes the cast so callers stay typed.
|
||||
export const patchRulePartial = (
|
||||
id: string,
|
||||
patch: Partial<RuletypesPostableRuleDTO>,
|
||||
): ReturnType<typeof patchRuleByID> =>
|
||||
patchRuleByID({ id }, patch as RuletypesPostableRuleDTO);
|
||||
20
frontend/src/api/alerts/put.ts
Normal file
20
frontend/src/api/alerts/put.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/save';
|
||||
|
||||
const put = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
const response = await axios.put(`/rules/${props.id}`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default put;
|
||||
18
frontend/src/api/alerts/save.ts
Normal file
18
frontend/src/api/alerts/save.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { isEmpty } from 'lodash-es';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/save';
|
||||
|
||||
import create from './create';
|
||||
import put from './put';
|
||||
|
||||
const save = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
if (props.id && !isEmpty(props.id)) {
|
||||
return put({ ...props });
|
||||
}
|
||||
|
||||
return create({ ...props });
|
||||
};
|
||||
|
||||
export default save;
|
||||
26
frontend/src/api/alerts/testAlert.ts
Normal file
26
frontend/src/api/alerts/testAlert.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/testAlert';
|
||||
|
||||
const testAlert = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/testRule', {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default testAlert;
|
||||
28
frontend/src/api/alerts/testAlertRule.ts
Normal file
28
frontend/src/api/alerts/testAlertRule.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
|
||||
|
||||
export interface TestAlertRuleResponse {
|
||||
data: {
|
||||
alertCount: number;
|
||||
message: string;
|
||||
};
|
||||
status: string;
|
||||
}
|
||||
|
||||
const testAlertRule = async (
|
||||
props: PostableAlertRuleV2,
|
||||
): Promise<SuccessResponse<TestAlertRuleResponse> | ErrorResponse> => {
|
||||
const response = await axios.post(`/testRule`, {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default testAlertRule;
|
||||
26
frontend/src/api/alerts/updateAlertRule.ts
Normal file
26
frontend/src/api/alerts/updateAlertRule.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
|
||||
|
||||
export interface UpdateAlertRuleResponse {
|
||||
data: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
const updateAlertRule = async (
|
||||
id: string,
|
||||
postableAlertRule: PostableAlertRuleV2,
|
||||
): Promise<SuccessResponse<UpdateAlertRuleResponse> | ErrorResponse> => {
|
||||
const response = await axios.put(`/rules/${id}`, {
|
||||
...postableAlertRule,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default updateAlertRule;
|
||||
@@ -1,6 +1,9 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const get = (key: string): string | null => {
|
||||
try {
|
||||
return localStorage.getItem(key);
|
||||
return localStorage.getItem(getScopedKey(key));
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const remove = (key: string): boolean => {
|
||||
try {
|
||||
window.localStorage.removeItem(key);
|
||||
localStorage.removeItem(getScopedKey(key));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const set = (key: string, value: string): boolean => {
|
||||
try {
|
||||
localStorage.setItem(key, value);
|
||||
localStorage.setItem(getScopedKey(key), value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
||||
12
frontend/src/api/browser/sessionstorage/get.ts
Normal file
12
frontend/src/api/browser/sessionstorage/get.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const get = (key: string): string | null => {
|
||||
try {
|
||||
return sessionStorage.getItem(getScopedKey(key));
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export default get;
|
||||
13
frontend/src/api/browser/sessionstorage/remove.ts
Normal file
13
frontend/src/api/browser/sessionstorage/remove.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const remove = (key: string): boolean => {
|
||||
try {
|
||||
sessionStorage.removeItem(getScopedKey(key));
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default remove;
|
||||
13
frontend/src/api/browser/sessionstorage/set.ts
Normal file
13
frontend/src/api/browser/sessionstorage/set.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
/* eslint-disable no-restricted-globals */
|
||||
import { getScopedKey } from 'utils/storage';
|
||||
|
||||
const set = (key: string, value: string): boolean => {
|
||||
try {
|
||||
sessionStorage.setItem(getScopedKey(key), value);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default set;
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
interceptorRejected,
|
||||
interceptorsRequestBasePath,
|
||||
interceptorsRequestResponse,
|
||||
interceptorsResponse,
|
||||
} from 'api';
|
||||
@@ -17,6 +18,7 @@ export const GeneratedAPIInstance = <T>(
|
||||
return generatedAPIAxiosInstance({ ...config }).then(({ data }) => data);
|
||||
};
|
||||
|
||||
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
|
||||
generatedAPIAxiosInstance.interceptors.response.use(
|
||||
interceptorsResponse,
|
||||
|
||||
@@ -11,6 +11,7 @@ import axios, {
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { Events } from 'constants/events';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { getBasePath } from 'utils/basePath';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
|
||||
import apiV1, { apiAlertManager, apiV2, apiV3, apiV4, apiV5 } from './apiV1';
|
||||
@@ -67,6 +68,39 @@ export const interceptorsRequestResponse = (
|
||||
return value;
|
||||
};
|
||||
|
||||
// Strips the leading '/' from path and joins with base — idempotent if already prefixed.
|
||||
// e.g. prependBase('/signoz/', '/api/v1/') → '/signoz/api/v1/'
|
||||
function prependBase(base: string, path: string): string {
|
||||
return path.startsWith(base) ? path : base + path.slice(1);
|
||||
}
|
||||
|
||||
// Prepends the runtime base path to outgoing requests so API calls work under
|
||||
// a URL prefix (e.g. /signoz/api/v1/…). No-op for root deployments and dev
|
||||
// (dev baseURL is a full http:// URL, not an absolute path).
|
||||
export const interceptorsRequestBasePath = (
|
||||
value: InternalAxiosRequestConfig,
|
||||
): InternalAxiosRequestConfig => {
|
||||
const basePath = getBasePath();
|
||||
if (basePath === '/') {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.baseURL?.startsWith('/')) {
|
||||
// Production relative baseURL: '/api/v1/' → '/signoz/api/v1/'
|
||||
value.baseURL = prependBase(basePath, value.baseURL);
|
||||
} else if (value.baseURL?.startsWith('http')) {
|
||||
// Dev absolute baseURL (VITE_FRONTEND_API_ENDPOINT): 'https://host/api/v1/' → 'https://host/signoz/api/v1/'
|
||||
const url = new URL(value.baseURL);
|
||||
url.pathname = prependBase(basePath, url.pathname);
|
||||
value.baseURL = url.toString();
|
||||
} else if (!value.baseURL && value.url?.startsWith('/')) {
|
||||
// Orval-generated client (empty baseURL, path in url): '/api/signoz/v1/rules' → '/signoz/api/signoz/v1/rules'
|
||||
value.url = prependBase(basePath, value.url);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export const interceptorRejected = async (
|
||||
value: AxiosResponse<any>,
|
||||
): Promise<AxiosResponse<any>> => {
|
||||
@@ -133,6 +167,7 @@ const instance = axios.create({
|
||||
});
|
||||
|
||||
instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
instance.interceptors.response.use(interceptorsResponse, interceptorRejected);
|
||||
|
||||
export const AxiosAlertManagerInstance = axios.create({
|
||||
@@ -147,6 +182,7 @@ ApiV2Instance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV2Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
ApiV2Instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
|
||||
// axios V3
|
||||
export const ApiV3Instance = axios.create({
|
||||
@@ -158,6 +194,7 @@ ApiV3Instance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV3Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
ApiV3Instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
//
|
||||
|
||||
// axios V4
|
||||
@@ -170,6 +207,7 @@ ApiV4Instance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV4Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
ApiV4Instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
//
|
||||
|
||||
// axios V5
|
||||
@@ -182,6 +220,7 @@ ApiV5Instance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
ApiV5Instance.interceptors.request.use(interceptorsRequestResponse);
|
||||
ApiV5Instance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
//
|
||||
|
||||
// axios Base
|
||||
@@ -194,6 +233,7 @@ LogEventAxiosInstance.interceptors.response.use(
|
||||
interceptorRejectedBase,
|
||||
);
|
||||
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
|
||||
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
//
|
||||
|
||||
AxiosAlertManagerInstance.interceptors.response.use(
|
||||
@@ -201,6 +241,7 @@ AxiosAlertManagerInstance.interceptors.response.use(
|
||||
interceptorRejected,
|
||||
);
|
||||
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestResponse);
|
||||
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestBasePath);
|
||||
|
||||
export { apiV1 };
|
||||
export default instance;
|
||||
|
||||
@@ -3,13 +3,16 @@ import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { ENVIRONMENT } from 'constants/env';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
// 10 min in ms
|
||||
const TIMEOUT_IN_MS = 10 * 60 * 1000;
|
||||
|
||||
export const LiveTail = (queryParams: string): EventSourcePolyfill =>
|
||||
new EventSourcePolyfill(
|
||||
`${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`,
|
||||
ENVIRONMENT.baseURL
|
||||
? `${ENVIRONMENT.baseURL}${apiV1}logs/tail?${queryParams}`
|
||||
: withBasePath(`${apiV1}logs/tail?${queryParams}`),
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${getLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN)}`,
|
||||
|
||||
45
frontend/src/api/plannedDowntime/createDowntimeSchedule.ts
Normal file
45
frontend/src/api/plannedDowntime/createDowntimeSchedule.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import { Recurrence } from './getAllDowntimeSchedules';
|
||||
|
||||
export interface DowntimeSchedulePayload {
|
||||
name: string;
|
||||
description?: string;
|
||||
alertIds: string[];
|
||||
schedule: {
|
||||
timezone?: string;
|
||||
startTime?: string | Dayjs;
|
||||
endTime?: string | Dayjs;
|
||||
recurrence?: Recurrence;
|
||||
};
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
status: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
const createDowntimeSchedule = async (
|
||||
props: DowntimeSchedulePayload,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/downtime_schedules', {
|
||||
...props,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default createDowntimeSchedule;
|
||||
19
frontend/src/api/plannedDowntime/deleteDowntimeSchedule.ts
Normal file
19
frontend/src/api/plannedDowntime/deleteDowntimeSchedule.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { useMutation, UseMutationResult } from 'react-query';
|
||||
import axios from 'api';
|
||||
|
||||
export interface DeleteDowntimeScheduleProps {
|
||||
id?: number;
|
||||
}
|
||||
|
||||
export interface DeleteSchedulePayloadProps {
|
||||
status: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
export const useDeleteDowntimeSchedule = (
|
||||
props: DeleteDowntimeScheduleProps,
|
||||
): UseMutationResult<DeleteSchedulePayloadProps, Error, number> =>
|
||||
useMutation({
|
||||
mutationKey: [props.id],
|
||||
mutationFn: () => axios.delete(`/downtime_schedules/${props.id}`),
|
||||
});
|
||||
51
frontend/src/api/plannedDowntime/getAllDowntimeSchedules.ts
Normal file
51
frontend/src/api/plannedDowntime/getAllDowntimeSchedules.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useQuery, UseQueryResult } from 'react-query';
|
||||
import axios from 'api';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { Option } from 'container/PlannedDowntime/PlannedDowntimeutils';
|
||||
|
||||
export type Recurrence = {
|
||||
startTime?: string | null;
|
||||
endTime?: string | null;
|
||||
duration?: number | string | null;
|
||||
repeatType?: string | Option | null;
|
||||
repeatOn?: string[] | null;
|
||||
};
|
||||
|
||||
type Schedule = {
|
||||
timezone: string | null;
|
||||
startTime: string | null;
|
||||
endTime: string | null;
|
||||
recurrence: Recurrence | null;
|
||||
};
|
||||
|
||||
export interface DowntimeSchedules {
|
||||
id: number;
|
||||
name: string | null;
|
||||
description: string | null;
|
||||
schedule: Schedule | null;
|
||||
alertIds: string[] | null;
|
||||
createdAt: string | null;
|
||||
createdBy: string | null;
|
||||
updatedAt: string | null;
|
||||
updatedBy: string | null;
|
||||
kind: string | null;
|
||||
}
|
||||
export type PayloadProps = { data: DowntimeSchedules[] };
|
||||
|
||||
export const getAllDowntimeSchedules = async (
|
||||
props?: GetAllDowntimeSchedulesPayloadProps,
|
||||
): Promise<AxiosResponse<PayloadProps>> =>
|
||||
axios.get('/downtime_schedules', { params: props });
|
||||
|
||||
export interface GetAllDowntimeSchedulesPayloadProps {
|
||||
active?: boolean;
|
||||
recurrence?: boolean;
|
||||
}
|
||||
|
||||
export const useGetAllDowntimeSchedules = (
|
||||
props?: GetAllDowntimeSchedulesPayloadProps,
|
||||
): UseQueryResult<AxiosResponse<PayloadProps>, AxiosError> =>
|
||||
useQuery<AxiosResponse<PayloadProps>, AxiosError>({
|
||||
queryKey: ['getAllDowntimeSchedules', props],
|
||||
queryFn: () => getAllDowntimeSchedules(props),
|
||||
});
|
||||
37
frontend/src/api/plannedDowntime/updateDowntimeSchedule.ts
Normal file
37
frontend/src/api/plannedDowntime/updateDowntimeSchedule.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
import { DowntimeSchedulePayload } from './createDowntimeSchedule';
|
||||
|
||||
export interface DowntimeScheduleUpdatePayload {
|
||||
data: DowntimeSchedulePayload;
|
||||
id?: number;
|
||||
}
|
||||
|
||||
export interface PayloadProps {
|
||||
status: string;
|
||||
data: string;
|
||||
}
|
||||
|
||||
const updateDowntimeSchedule = async (
|
||||
props: DowntimeScheduleUpdatePayload,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/downtime_schedules/${props.id}`, {
|
||||
...props.data,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default updateDowntimeSchedule;
|
||||
@@ -12,6 +12,7 @@ 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 { withBasePath } from 'utils/basePath';
|
||||
|
||||
export interface NavigateToExplorerProps {
|
||||
filters: TagFilterItem[];
|
||||
@@ -133,7 +134,7 @@ export function useNavigateToExplorer(): (
|
||||
QueryParams.compositeQuery
|
||||
}=${JSONCompositeQuery}`;
|
||||
|
||||
window.open(newExplorerPath, sameTab ? '_self' : '_blank');
|
||||
window.open(withBasePath(newExplorerPath), sameTab ? '_self' : '_blank');
|
||||
},
|
||||
[
|
||||
prepareQuery,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CreditCard, MessageSquareText, X } from 'lucide-react';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
export default function ChatSupportGateway(): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
@@ -54,7 +55,7 @@ export default function ChatSupportGateway(): JSX.Element {
|
||||
});
|
||||
|
||||
updateCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import APIError from 'types/api/error';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
import { toAPIError } from 'utils/errorUtils';
|
||||
|
||||
import DeleteMemberDialog from './DeleteMemberDialog';
|
||||
@@ -387,7 +388,7 @@ function EditMemberDrawer({
|
||||
pathParams: { id: member.id },
|
||||
});
|
||||
if (response?.data?.token) {
|
||||
const link = `${window.location.origin}/password-reset?token=${response.data.token}`;
|
||||
const link = getAbsoluteUrl(`/password-reset?token=${response.data.token}`);
|
||||
setResetLink(link);
|
||||
setResetLinkExpiresAt(
|
||||
response.data.expiresAt
|
||||
|
||||
@@ -13,6 +13,7 @@ import GetMinMax from 'lib/getMinMax';
|
||||
import { Check, Info, Link2 } from 'lucide-react';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
const routesToBeSharedWithTime = [
|
||||
ROUTES.LOGS_EXPLORER,
|
||||
@@ -80,17 +81,13 @@ function ShareURLModal(): JSX.Element {
|
||||
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
|
||||
currentUrl = `${window.location.origin}${
|
||||
location.pathname
|
||||
}?${urlQuery.toString()}`;
|
||||
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
|
||||
} else {
|
||||
urlQuery.delete(QueryParams.startTime);
|
||||
urlQuery.delete(QueryParams.endTime);
|
||||
|
||||
urlQuery.set(QueryParams.relativeTime, selectedTime);
|
||||
currentUrl = `${window.location.origin}${
|
||||
location.pathname
|
||||
}?${urlQuery.toString()}`;
|
||||
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
import { ROLES } from 'types/roles';
|
||||
import { EMAIL_REGEX } from 'utils/app';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@@ -188,7 +189,7 @@ function InviteMembersModal({
|
||||
email: row.email.trim(),
|
||||
name: '',
|
||||
role: row.role as ROLES,
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
});
|
||||
} else {
|
||||
await inviteUsers({
|
||||
@@ -196,7 +197,7 @@ function InviteMembersModal({
|
||||
email: row.email.trim(),
|
||||
name: '',
|
||||
role: row.role,
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,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 { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
import './LaunchChatSupport.styles.scss';
|
||||
|
||||
@@ -154,7 +155,7 @@ function LaunchChatSupport({
|
||||
});
|
||||
|
||||
updateCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import { ArrowUpRight } from 'lucide-react';
|
||||
import { openInNewTab } 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');
|
||||
openInNewTab(url);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
KAFKA_SETUP_DOC_LINK,
|
||||
MessagingQueueHealthCheckService,
|
||||
} from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { openInNewTab } 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');
|
||||
openInNewTab(KAFKA_SETUP_DOC_LINK);
|
||||
}
|
||||
};
|
||||
return {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import getSessionStorageApi from 'api/browser/sessionstorage/get';
|
||||
import removeSessionStorageApi from 'api/browser/sessionstorage/remove';
|
||||
import setSessionStorageApi from 'api/browser/sessionstorage/set';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const PREVIOUS_QUERY_KEY = 'previousQuery';
|
||||
|
||||
function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
|
||||
try {
|
||||
const raw = sessionStorage.getItem(PREVIOUS_QUERY_KEY);
|
||||
const raw = getSessionStorageApi(PREVIOUS_QUERY_KEY);
|
||||
if (!raw) {
|
||||
return {};
|
||||
}
|
||||
@@ -17,7 +20,7 @@ function getPreviousQueryFromStore(): Record<string, IBuilderQuery> {
|
||||
|
||||
function writePreviousQueryToStore(store: Record<string, IBuilderQuery>): void {
|
||||
try {
|
||||
sessionStorage.setItem(PREVIOUS_QUERY_KEY, JSON.stringify(store));
|
||||
setSessionStorageApi(PREVIOUS_QUERY_KEY, JSON.stringify(store));
|
||||
} catch {
|
||||
// ignore quota or serialization errors
|
||||
}
|
||||
@@ -63,7 +66,7 @@ export const removeKeyFromPreviousQuery = (key: string): void => {
|
||||
|
||||
export const clearPreviousQuery = (): void => {
|
||||
try {
|
||||
sessionStorage.removeItem(PREVIOUS_QUERY_KEY);
|
||||
removeSessionStorageApi(PREVIOUS_QUERY_KEY);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
@@ -108,8 +108,7 @@ function DynamicColumnTable({
|
||||
// Update URL with new page number while preserving other params
|
||||
urlQuery.set('page', page.toString());
|
||||
|
||||
const newUrl = `${window.location.pathname}?${urlQuery.toString()}`;
|
||||
safeNavigate(newUrl);
|
||||
safeNavigate({ search: `?${urlQuery.toString()}` });
|
||||
|
||||
// Call original pagination handler if provided
|
||||
if (pagination?.onChange && !!pageSize) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
|
||||
import { DynamicColumnsKey } from './contants';
|
||||
import {
|
||||
GetNewColumnDataFunction,
|
||||
@@ -12,7 +15,7 @@ export const getVisibleColumns: GetVisibleColumnsFunction = ({
|
||||
}) => {
|
||||
let columnVisibilityData: { [key: string]: boolean };
|
||||
try {
|
||||
const storedData = localStorage.getItem(tablesource);
|
||||
const storedData = getLocalStorageKey(tablesource);
|
||||
if (typeof storedData === 'string' && dynamicColumns) {
|
||||
columnVisibilityData = JSON.parse(storedData);
|
||||
return dynamicColumns.filter((column) => {
|
||||
@@ -28,7 +31,7 @@ export const getVisibleColumns: GetVisibleColumnsFunction = ({
|
||||
initialColumnVisibility[key] = false;
|
||||
});
|
||||
|
||||
localStorage.setItem(tablesource, JSON.stringify(initialColumnVisibility));
|
||||
setLocalStorageKey(tablesource, JSON.stringify(initialColumnVisibility));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@@ -42,14 +45,14 @@ export const setVisibleColumns = ({
|
||||
dynamicColumns,
|
||||
}: SetVisibleColumnsProps): void => {
|
||||
try {
|
||||
const storedData = localStorage.getItem(tablesource);
|
||||
const storedData = getLocalStorageKey(tablesource);
|
||||
if (typeof storedData === 'string' && dynamicColumns) {
|
||||
const columnVisibilityData = JSON.parse(storedData);
|
||||
const { key } = dynamicColumns[index];
|
||||
if (key) {
|
||||
columnVisibilityData[key] = checked;
|
||||
}
|
||||
localStorage.setItem(tablesource, JSON.stringify(columnVisibilityData));
|
||||
setLocalStorageKey(tablesource, JSON.stringify(columnVisibilityData));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { RuletypesAlertTypeDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
@@ -9,17 +8,3 @@ export const ALERTS_DATA_SOURCE_MAP: Record<AlertTypes, DataSource> = {
|
||||
[AlertTypes.TRACES_BASED_ALERT]: DataSource.TRACES,
|
||||
[AlertTypes.EXCEPTIONS_BASED_ALERT]: DataSource.TRACES,
|
||||
};
|
||||
|
||||
export function dataSourceForAlertType(
|
||||
alertType: RuletypesAlertTypeDTO | undefined,
|
||||
): DataSource {
|
||||
switch (alertType) {
|
||||
case RuletypesAlertTypeDTO.LOGS_BASED_ALERT:
|
||||
return DataSource.LOGS;
|
||||
case RuletypesAlertTypeDTO.TRACES_BASED_ALERT:
|
||||
case RuletypesAlertTypeDTO.EXCEPTIONS_BASED_ALERT:
|
||||
return DataSource.TRACES;
|
||||
default:
|
||||
return DataSource.METRICS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -94,20 +95,14 @@ function DependentServices({
|
||||
}}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => {
|
||||
const url = new URL(
|
||||
`/services/${
|
||||
record.serviceData.serviceName &&
|
||||
record.serviceData.serviceName !== '-'
|
||||
? record.serviceData.serviceName
|
||||
: ''
|
||||
}`,
|
||||
window.location.origin,
|
||||
);
|
||||
const serviceName =
|
||||
record.serviceData.serviceName && record.serviceData.serviceName !== '-'
|
||||
? record.serviceData.serviceName
|
||||
: '';
|
||||
const urlQuery = new URLSearchParams();
|
||||
urlQuery.set(QueryParams.startTime, timeRange.startTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, timeRange.endTime.toString());
|
||||
url.search = urlQuery.toString();
|
||||
window.open(url.toString(), '_blank');
|
||||
openInNewTab(`/services/${serviceName}?${urlQuery.toString()}`);
|
||||
},
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
import { UserPreference } from 'types/api/preferences/preference';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { showErrorNotification } from 'utils/error';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
import {
|
||||
@@ -461,7 +462,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
const handleFailedPayment = useCallback((): void => {
|
||||
manageCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
}, [manageCreditCard]);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import { isEmpty, pick } from 'lodash-es';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
|
||||
|
||||
import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph';
|
||||
@@ -324,7 +325,7 @@ export default function BillingContainer(): JSX.Element {
|
||||
});
|
||||
|
||||
updateCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
} else {
|
||||
logEvent('Billing : Manage Billing', {
|
||||
@@ -333,7 +334,7 @@ export default function BillingContainer(): JSX.Element {
|
||||
});
|
||||
|
||||
manageCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -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 { openInNewTab } 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');
|
||||
openInNewTab(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.
|
||||
|
||||
@@ -6,7 +6,9 @@ import { getCreateAlertLocalStateFromAlertDef } from 'container/CreateAlertV2/ut
|
||||
import * as useSafeNavigateHook from 'hooks/useSafeNavigate';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
|
||||
import * as rulesHook from '../../../../api/generated/services/rules';
|
||||
import * as useCreateAlertRuleHook from '../../../../hooks/alerts/useCreateAlertRule';
|
||||
import * as useTestAlertRuleHook from '../../../../hooks/alerts/useTestAlertRule';
|
||||
import * as useUpdateAlertRuleHook from '../../../../hooks/alerts/useUpdateAlertRule';
|
||||
import { CreateAlertProvider } from '../../context';
|
||||
import CreateAlertHeader from '../CreateAlertHeader';
|
||||
|
||||
@@ -15,15 +17,15 @@ jest.spyOn(useSafeNavigateHook, 'useSafeNavigate').mockReturnValue({
|
||||
safeNavigate: mockSafeNavigate,
|
||||
});
|
||||
|
||||
jest.spyOn(rulesHook, 'useCreateRule').mockReturnValue({
|
||||
jest.spyOn(useCreateAlertRuleHook, 'useCreateAlertRule').mockReturnValue({
|
||||
mutate: jest.fn(),
|
||||
isLoading: false,
|
||||
} as any);
|
||||
jest.spyOn(rulesHook, 'useTestRule').mockReturnValue({
|
||||
jest.spyOn(useTestAlertRuleHook, 'useTestAlertRule').mockReturnValue({
|
||||
mutate: jest.fn(),
|
||||
isLoading: false,
|
||||
} as any);
|
||||
jest.spyOn(rulesHook, 'useUpdateRuleByID').mockReturnValue({
|
||||
jest.spyOn(useUpdateAlertRuleHook, 'useUpdateAlertRule').mockReturnValue({
|
||||
mutate: jest.fn(),
|
||||
isLoading: false,
|
||||
} as any);
|
||||
|
||||
@@ -34,7 +34,6 @@ export const createMockAlertContextState = (
|
||||
isUpdatingAlertRule: false,
|
||||
updateAlertRule: jest.fn(),
|
||||
isEditMode: false,
|
||||
ruleId: '',
|
||||
...overrides,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { Button, Tooltip, Typography } from 'antd';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { Check, Loader, Send, X } from 'lucide-react';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { toPostableRuleDTO } from 'types/api/alerts/convert';
|
||||
import APIError from 'types/api/error';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
|
||||
import { useCreateAlertState } from '../context';
|
||||
@@ -36,20 +30,9 @@ function Footer(): JSX.Element {
|
||||
updateAlertRule,
|
||||
isUpdatingAlertRule,
|
||||
isEditMode,
|
||||
ruleId,
|
||||
} = useCreateAlertState();
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const handleApiError = useCallback(
|
||||
(error: unknown): void => {
|
||||
showErrorModal(
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) as APIError,
|
||||
);
|
||||
},
|
||||
[showErrorModal],
|
||||
);
|
||||
|
||||
const handleDiscard = (e: React.MouseEvent): void => {
|
||||
discardAlertRule();
|
||||
@@ -88,21 +71,20 @@ function Footer(): JSX.Element {
|
||||
notificationSettings,
|
||||
query: currentQuery,
|
||||
});
|
||||
testAlertRule(
|
||||
{ data: toPostableRuleDTO(payload) },
|
||||
{
|
||||
onSuccess: (response) => {
|
||||
if (response.data?.alertCount === 0) {
|
||||
toast.error(
|
||||
'No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
toast.success('Test notification sent successfully');
|
||||
},
|
||||
onError: handleApiError,
|
||||
testAlertRule(payload, {
|
||||
onSuccess: (response) => {
|
||||
if (response.payload?.data?.alertCount === 0) {
|
||||
toast.error(
|
||||
'No alerts found during the evaluation. This happens when rule condition is unsatisfied. You may adjust the rule threshold and retry.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
toast.success('Test notification sent successfully');
|
||||
},
|
||||
);
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
}, [
|
||||
alertType,
|
||||
basicAlertState,
|
||||
@@ -125,30 +107,25 @@ function Footer(): JSX.Element {
|
||||
query: currentQuery,
|
||||
});
|
||||
if (isEditMode) {
|
||||
updateAlertRule(
|
||||
{
|
||||
pathParams: { id: ruleId },
|
||||
data: toPostableRuleDTO(payload),
|
||||
updateAlertRule(payload, {
|
||||
onSuccess: () => {
|
||||
toast.success('Alert rule updated successfully');
|
||||
safeNavigate('/alerts');
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success('Alert rule updated successfully');
|
||||
safeNavigate('/alerts');
|
||||
},
|
||||
onError: handleApiError,
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
);
|
||||
});
|
||||
} else {
|
||||
createAlertRule(
|
||||
{ data: toPostableRuleDTO(payload) },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success('Alert rule created successfully');
|
||||
safeNavigate('/alerts');
|
||||
},
|
||||
onError: handleApiError,
|
||||
createAlertRule(payload, {
|
||||
onSuccess: () => {
|
||||
toast.success('Alert rule created successfully');
|
||||
safeNavigate('/alerts');
|
||||
},
|
||||
);
|
||||
onError: (error) => {
|
||||
toast.error(error.message);
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [
|
||||
alertType,
|
||||
@@ -159,11 +136,9 @@ function Footer(): JSX.Element {
|
||||
notificationSettings,
|
||||
currentQuery,
|
||||
isEditMode,
|
||||
ruleId,
|
||||
updateAlertRule,
|
||||
createAlertRule,
|
||||
safeNavigate,
|
||||
handleApiError,
|
||||
]);
|
||||
|
||||
const disableButtons =
|
||||
|
||||
@@ -12,11 +12,6 @@ import Footer from '../Footer';
|
||||
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
|
||||
useQueryBuilder: jest.fn(),
|
||||
}));
|
||||
jest.mock('providers/ErrorModalProvider', () => ({
|
||||
useErrorModal: (): { showErrorModal: jest.Mock } => ({
|
||||
showErrorModal: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: jest.fn(),
|
||||
|
||||
@@ -10,13 +10,11 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import {
|
||||
useCreateRule,
|
||||
useTestRule,
|
||||
useUpdateRuleByID,
|
||||
} from 'api/generated/services/rules';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { AlertDetectionTypes } from 'container/FormAlertRules';
|
||||
import { useCreateAlertRule } from 'hooks/alerts/useCreateAlertRule';
|
||||
import { useTestAlertRule } from 'hooks/alerts/useTestAlertRule';
|
||||
import { useUpdateAlertRule } from 'hooks/alerts/useUpdateAlertRule';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
@@ -217,14 +215,17 @@ export function CreateAlertProvider(
|
||||
const {
|
||||
mutate: createAlertRule,
|
||||
isLoading: isCreatingAlertRule,
|
||||
} = useCreateRule();
|
||||
} = useCreateAlertRule();
|
||||
|
||||
const { mutate: testAlertRule, isLoading: isTestingAlertRule } = useTestRule();
|
||||
const {
|
||||
mutate: testAlertRule,
|
||||
isLoading: isTestingAlertRule,
|
||||
} = useTestAlertRule();
|
||||
|
||||
const {
|
||||
mutate: updateAlertRule,
|
||||
isLoading: isUpdatingAlertRule,
|
||||
} = useUpdateRuleByID();
|
||||
} = useUpdateAlertRule(ruleId || '');
|
||||
|
||||
const contextValue: ICreateAlertContextProps = useMemo(
|
||||
() => ({
|
||||
@@ -248,7 +249,6 @@ export function CreateAlertProvider(
|
||||
updateAlertRule,
|
||||
isUpdatingAlertRule,
|
||||
isEditMode: isEditMode || false,
|
||||
ruleId: ruleId || '',
|
||||
}),
|
||||
[
|
||||
createAlertState,
|
||||
@@ -267,7 +267,6 @@ export function CreateAlertProvider(
|
||||
updateAlertRule,
|
||||
isUpdatingAlertRule,
|
||||
isEditMode,
|
||||
ruleId,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { Dispatch } from 'react';
|
||||
import { UseMutateFunction } from 'react-query';
|
||||
import type {
|
||||
CreateRule201,
|
||||
RenderErrorResponseDTO,
|
||||
RuletypesPostableRuleDTO,
|
||||
TestRule200,
|
||||
UpdateRuleByIDPathParameters,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import type { BodyType, ErrorType } from 'api/generatedAPIInstance';
|
||||
import { CreateAlertRuleResponse } from 'api/alerts/createAlertRule';
|
||||
import { TestAlertRuleResponse } from 'api/alerts/testAlertRule';
|
||||
import { UpdateAlertRuleResponse } from 'api/alerts/updateAlertRule';
|
||||
import { Dayjs } from 'dayjs';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { PostableAlertRuleV2 } from 'types/api/alerts/alertTypesV2';
|
||||
import { Labels } from 'types/api/alerts/def';
|
||||
|
||||
export interface ICreateAlertContextProps {
|
||||
@@ -27,33 +24,27 @@ export interface ICreateAlertContextProps {
|
||||
setNotificationSettings: Dispatch<NotificationSettingsAction>;
|
||||
isCreatingAlertRule: boolean;
|
||||
createAlertRule: UseMutateFunction<
|
||||
CreateRule201,
|
||||
ErrorType<unknown>,
|
||||
{ data: BodyType<RuletypesPostableRuleDTO> },
|
||||
SuccessResponse<CreateAlertRuleResponse, unknown> | ErrorResponse,
|
||||
Error,
|
||||
PostableAlertRuleV2,
|
||||
unknown
|
||||
>;
|
||||
isTestingAlertRule: boolean;
|
||||
testAlertRule: UseMutateFunction<
|
||||
TestRule200,
|
||||
ErrorType<unknown>,
|
||||
{ data: BodyType<RuletypesPostableRuleDTO> },
|
||||
SuccessResponse<TestAlertRuleResponse, unknown> | ErrorResponse,
|
||||
Error,
|
||||
PostableAlertRuleV2,
|
||||
unknown
|
||||
>;
|
||||
discardAlertRule: () => void;
|
||||
isUpdatingAlertRule: boolean;
|
||||
updateAlertRule: UseMutateFunction<
|
||||
Awaited<
|
||||
ReturnType<typeof import('api/generated/services/rules').updateRuleByID>
|
||||
>,
|
||||
ErrorType<RenderErrorResponseDTO>,
|
||||
{
|
||||
pathParams: UpdateRuleByIDPathParameters;
|
||||
data: BodyType<RuletypesPostableRuleDTO>;
|
||||
},
|
||||
SuccessResponse<UpdateAlertRuleResponse, unknown> | ErrorResponse,
|
||||
Error,
|
||||
PostableAlertRuleV2,
|
||||
unknown
|
||||
>;
|
||||
isEditMode: boolean;
|
||||
ruleId: string;
|
||||
}
|
||||
|
||||
export interface ICreateAlertProviderProps {
|
||||
|
||||
@@ -48,6 +48,7 @@ function DomainUpdateToast({
|
||||
className="custom-domain-toast-visit-btn"
|
||||
suffixIcon={<ExternalLink size={12} />}
|
||||
onClick={(): void => {
|
||||
// eslint-disable-next-line rulesdir/no-raw-absolute-path
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -16,6 +16,8 @@ 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 { getAbsoluteUrl } from 'utils/basePath';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import './PublicDashboard.styles.scss';
|
||||
|
||||
@@ -213,7 +215,7 @@ function PublicDashboardSetting(): JSX.Element {
|
||||
|
||||
try {
|
||||
setCopyPublicDashboardURL(
|
||||
`${window.location.origin}${publicDashboardResponse?.data?.publicPath}`,
|
||||
getAbsoluteUrl(publicDashboardResponse?.data?.publicPath ?? ''),
|
||||
);
|
||||
toast.success('Copied Public Dashboard URL successfully');
|
||||
} catch (error) {
|
||||
@@ -222,7 +224,7 @@ function PublicDashboardSetting(): JSX.Element {
|
||||
};
|
||||
|
||||
const publicDashboardURL = useMemo(
|
||||
() => `${window.location.origin}${publicDashboardResponse?.data?.publicPath}`,
|
||||
() => getAbsoluteUrl(publicDashboardResponse?.data?.publicPath ?? ''),
|
||||
[publicDashboardResponse],
|
||||
);
|
||||
|
||||
@@ -294,7 +296,7 @@ function PublicDashboardSetting(): JSX.Element {
|
||||
icon={<ExternalLink size={12} />}
|
||||
onClick={(): void => {
|
||||
if (publicDashboardURL) {
|
||||
window.open(publicDashboardURL, '_blank');
|
||||
openInNewTab(publicDashboardURL);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import getSessionStorageApi from 'api/browser/sessionstorage/get';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY } from 'hooks/dashboard/useDashboardsListQueryParams';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
@@ -26,7 +27,7 @@ function DashboardBreadcrumbs(): JSX.Element {
|
||||
const { title = '', image = Base64Icons[0] } = selectedData || {};
|
||||
|
||||
const goToListPage = useCallback(() => {
|
||||
const dashboardsListQueryParamsString = sessionStorage.getItem(
|
||||
const dashboardsListQueryParamsString = getSessionStorageApi(
|
||||
DASHBOARDS_LIST_QUERY_PARAMS_STORAGE_KEY,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import removeLocalStorageKey from 'api/browser/localstorage/remove';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
|
||||
import { GraphVisibilityState, SeriesVisibilityItem } from '../types';
|
||||
@@ -12,7 +15,7 @@ export function getStoredSeriesVisibility(
|
||||
widgetId: string,
|
||||
): SeriesVisibilityItem[] | null {
|
||||
try {
|
||||
const storedData = localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
|
||||
const storedData = getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
|
||||
|
||||
if (!storedData) {
|
||||
return null;
|
||||
@@ -29,7 +32,7 @@ export function getStoredSeriesVisibility(
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
// If the stored data is malformed, remove it
|
||||
localStorage.removeItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
|
||||
removeLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
|
||||
}
|
||||
// Silently handle parsing errors - fall back to default visibility
|
||||
return null;
|
||||
@@ -42,7 +45,7 @@ export function updateSeriesVisibilityToLocalStorage(
|
||||
): void {
|
||||
let visibilityStates: GraphVisibilityState[] = [];
|
||||
try {
|
||||
const storedData = localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
|
||||
const storedData = getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES);
|
||||
visibilityStates = JSON.parse(storedData || '[]');
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
@@ -63,7 +66,7 @@ export function updateSeriesVisibilityToLocalStorage(
|
||||
];
|
||||
}
|
||||
|
||||
localStorage.setItem(
|
||||
setLocalStorageKey(
|
||||
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
|
||||
JSON.stringify(visibilityStates),
|
||||
);
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { RuletypesAlertTypeDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { dataSourceForAlertType } from 'constants/alerts';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import { initialQueryBuilderFormValuesMap } from 'constants/queryBuilder';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export function sanitizeDefaultAlertQuery(
|
||||
query: Query,
|
||||
alertType: RuletypesAlertTypeDTO | undefined,
|
||||
alertType: AlertTypes,
|
||||
): Query {
|
||||
// If there are no queries, add a default one based on the alert type
|
||||
if (query.builder.queryData.length === 0) {
|
||||
const dataSource = dataSourceForAlertType(alertType);
|
||||
const dataSource = ALERTS_DATA_SOURCE_MAP[alertType];
|
||||
query.builder.queryData.push(initialQueryBuilderFormValuesMap[dataSource]);
|
||||
}
|
||||
return query;
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { TelemetryFieldKey } from 'api/v5/v5';
|
||||
import axios from 'axios';
|
||||
@@ -472,7 +474,7 @@ function ExplorerOptions({
|
||||
value: string;
|
||||
}): void => {
|
||||
// Retrieve stored views from local storage
|
||||
const storedViews = localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
|
||||
const storedViews = getLocalStorageKey(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
|
||||
|
||||
// Initialize or parse the stored views
|
||||
const updatedViews: PreservedViewsInLocalStorage = storedViews
|
||||
@@ -486,7 +488,7 @@ function ExplorerOptions({
|
||||
};
|
||||
|
||||
// Save the updated views back to local storage
|
||||
localStorage.setItem(
|
||||
setLocalStorageKey(
|
||||
PRESERVED_VIEW_LOCAL_STORAGE_KEY,
|
||||
JSON.stringify(updatedViews),
|
||||
);
|
||||
@@ -537,7 +539,7 @@ function ExplorerOptions({
|
||||
|
||||
const removeCurrentViewFromLocalStorage = (): void => {
|
||||
// Retrieve stored views from local storage
|
||||
const storedViews = localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
|
||||
const storedViews = getLocalStorageKey(PRESERVED_VIEW_LOCAL_STORAGE_KEY);
|
||||
|
||||
if (storedViews) {
|
||||
// Parse the stored views
|
||||
@@ -547,7 +549,7 @@ function ExplorerOptions({
|
||||
delete parsedViews[PRESERVED_VIEW_TYPE];
|
||||
|
||||
// Update local storage with the modified views
|
||||
localStorage.setItem(
|
||||
setLocalStorageKey(
|
||||
PRESERVED_VIEW_LOCAL_STORAGE_KEY,
|
||||
JSON.stringify(parsedViews),
|
||||
);
|
||||
@@ -672,7 +674,7 @@ function ExplorerOptions({
|
||||
}
|
||||
|
||||
const parsedPreservedView = JSON.parse(
|
||||
localStorage.getItem(PRESERVED_VIEW_LOCAL_STORAGE_KEY) || '{}',
|
||||
getLocalStorageKey(PRESERVED_VIEW_LOCAL_STORAGE_KEY) || '{}',
|
||||
);
|
||||
|
||||
const preservedView = parsedPreservedView[PRESERVED_VIEW_TYPE] || {};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
import { showErrorNotification } from 'components/ExplorerCard/utils';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { QueryParams } from 'constants/query';
|
||||
@@ -71,7 +73,7 @@ export const generateRGBAFromHex = (hex: string, opacity: number): string =>
|
||||
|
||||
export const getExplorerToolBarVisibility = (dataSource: string): boolean => {
|
||||
try {
|
||||
const showExplorerToolbar = localStorage.getItem(
|
||||
const showExplorerToolbar = getLocalStorageKey(
|
||||
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
|
||||
);
|
||||
if (showExplorerToolbar === null) {
|
||||
@@ -84,7 +86,7 @@ export const getExplorerToolBarVisibility = (dataSource: string): boolean => {
|
||||
[DataSource.TRACES]: true,
|
||||
[DataSource.LOGS]: true,
|
||||
};
|
||||
localStorage.setItem(
|
||||
setLocalStorageKey(
|
||||
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
|
||||
JSON.stringify(parsedShowExplorerToolbar),
|
||||
);
|
||||
@@ -103,13 +105,13 @@ export const setExplorerToolBarVisibility = (
|
||||
dataSource: string,
|
||||
): void => {
|
||||
try {
|
||||
const showExplorerToolbar = localStorage.getItem(
|
||||
const showExplorerToolbar = getLocalStorageKey(
|
||||
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
|
||||
);
|
||||
if (showExplorerToolbar) {
|
||||
const parsedShowExplorerToolbar = JSON.parse(showExplorerToolbar);
|
||||
parsedShowExplorerToolbar[dataSource] = value;
|
||||
localStorage.setItem(
|
||||
setLocalStorageKey(
|
||||
LOCALSTORAGE.SHOW_EXPLORER_TOOLBAR,
|
||||
JSON.stringify(parsedShowExplorerToolbar),
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import APIError from 'types/api/error';
|
||||
import { OrgSessionContext } from 'types/api/v2/sessions/context/get';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
import tvUrl from '@/assets/svgs/tv.svg';
|
||||
|
||||
@@ -105,7 +106,7 @@ function ForgotPassword({
|
||||
data: {
|
||||
email: values.email,
|
||||
orgId: currentOrgId,
|
||||
frontendBaseURL: window.location.origin,
|
||||
frontendBaseURL: getBaseUrl(),
|
||||
},
|
||||
});
|
||||
}, [form, forgotPasswordMutate, initialOrgId, hasMultipleOrgs]);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -6,15 +6,9 @@ import { useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons';
|
||||
import { Button, FormInstance, Modal, SelectProps, Typography } from 'antd';
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import testAlertApi from 'api/alerts/testAlert';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
createRule,
|
||||
testRule,
|
||||
updateRuleByID,
|
||||
} from 'api/generated/services/rules';
|
||||
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import { getInvolvedQueriesInTraceOperator } from 'components/QueryBuilderV2/QueryV2/TraceOperator/utils/utils';
|
||||
import YAxisUnitSelector from 'components/YAxisUnitSelector';
|
||||
import { YAxisSource } from 'components/YAxisUnitSelector/types';
|
||||
@@ -38,16 +32,13 @@ import { isEmpty, isEqual } from 'lodash-es';
|
||||
import { BellDot, ExternalLink } from 'lucide-react';
|
||||
import Tabs2 from 'periscope/components/Tabs2';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { toPostableRuleDTOFromAlertDef } from 'types/api/alerts/convert';
|
||||
import {
|
||||
AlertDef,
|
||||
defaultEvalWindow,
|
||||
defaultMatchType,
|
||||
} from 'types/api/alerts/def';
|
||||
import APIError from 'types/api/error';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { QueryFunction } from 'types/api/v5/queryRange';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
@@ -55,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 { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import BasicInfo from './BasicInfo';
|
||||
import ChartPreview from './ChartPreview';
|
||||
@@ -377,7 +369,6 @@ function FormAlertRules({
|
||||
redirectWithQueryBuilderData(query);
|
||||
};
|
||||
const { notifications } = useNotifications();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const validatePromParams = useCallback((): boolean => {
|
||||
let retval = true;
|
||||
@@ -543,47 +534,59 @@ function FormAlertRules({
|
||||
};
|
||||
|
||||
try {
|
||||
if (ruleId && !isEmpty(ruleId)) {
|
||||
await updateRuleByID(
|
||||
{ id: ruleId },
|
||||
toPostableRuleDTOFromAlertDef(postableAlert),
|
||||
);
|
||||
const apiReq =
|
||||
ruleId && !isEmpty(ruleId)
|
||||
? { data: postableAlert, id: ruleId }
|
||||
: { data: postableAlert };
|
||||
|
||||
const response = await saveAlertApi(apiReq);
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
logData = {
|
||||
status: 'success',
|
||||
statusMessage: isNewRule ? t('rule_created') : t('rule_edited'),
|
||||
};
|
||||
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: logData.statusMessage,
|
||||
});
|
||||
|
||||
// invalidate rule in cache
|
||||
ruleCache.invalidateQueries([
|
||||
REACT_QUERY_KEY.ALERT_RULE_DETAILS,
|
||||
`${ruleId}`,
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
setTimeout(() => {
|
||||
urlQuery.delete(QueryParams.compositeQuery);
|
||||
urlQuery.delete(QueryParams.panelTypes);
|
||||
urlQuery.delete(QueryParams.ruleId);
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
safeNavigate(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
|
||||
}, 2000);
|
||||
} else {
|
||||
await createRule(toPostableRuleDTOFromAlertDef(postableAlert));
|
||||
logData = {
|
||||
status: 'error',
|
||||
statusMessage: response.error || t('unexpected_error'),
|
||||
};
|
||||
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: logData.statusMessage,
|
||||
});
|
||||
}
|
||||
|
||||
logData = {
|
||||
status: 'success',
|
||||
statusMessage: isNewRule ? t('rule_created') : t('rule_edited'),
|
||||
};
|
||||
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: logData.statusMessage,
|
||||
});
|
||||
|
||||
// invalidate rule in cache
|
||||
ruleCache.invalidateQueries([
|
||||
REACT_QUERY_KEY.ALERT_RULE_DETAILS,
|
||||
`${ruleId}`,
|
||||
]);
|
||||
|
||||
// eslint-disable-next-line sonarjs/no-identical-functions
|
||||
setTimeout(() => {
|
||||
urlQuery.delete(QueryParams.compositeQuery);
|
||||
urlQuery.delete(QueryParams.panelTypes);
|
||||
urlQuery.delete(QueryParams.ruleId);
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
safeNavigate(`${ROUTES.LIST_ALL_ALERT}?${urlQuery.toString()}`);
|
||||
}, 2000);
|
||||
} catch (e) {
|
||||
const apiError = convertToApiError(e as AxiosError<RenderErrorResponseDTO>);
|
||||
logData = {
|
||||
status: 'error',
|
||||
statusMessage: apiError?.getErrorMessage() || t('unexpected_error'),
|
||||
statusMessage: t('unexpected_error'),
|
||||
};
|
||||
|
||||
showErrorModal(apiError as APIError);
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: logData.statusMessage,
|
||||
});
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
@@ -639,30 +642,39 @@ function FormAlertRules({
|
||||
let statusResponse = { status: 'failed', message: '' };
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await testRule(
|
||||
toPostableRuleDTOFromAlertDef(postableAlert),
|
||||
);
|
||||
const response = await testAlertApi({ data: postableAlert });
|
||||
|
||||
if (response.data?.alertCount === 0) {
|
||||
if (response.statusCode === 200) {
|
||||
const { payload } = response;
|
||||
if (payload?.alertCount === 0) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('no_alerts_found'),
|
||||
});
|
||||
statusResponse = { status: 'failed', message: t('no_alerts_found') };
|
||||
} else {
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: t('rule_test_fired'),
|
||||
});
|
||||
statusResponse = { status: 'success', message: t('rule_test_fired') };
|
||||
}
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('no_alerts_found'),
|
||||
description: response.error || t('unexpected_error'),
|
||||
});
|
||||
statusResponse = { status: 'failed', message: t('no_alerts_found') };
|
||||
} else {
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: t('rule_test_fired'),
|
||||
});
|
||||
statusResponse = { status: 'success', message: t('rule_test_fired') };
|
||||
statusResponse = {
|
||||
status: 'failed',
|
||||
message: response.error || t('unexpected_error'),
|
||||
};
|
||||
}
|
||||
} catch (e) {
|
||||
const apiError = convertToApiError(e as AxiosError<RenderErrorResponseDTO>);
|
||||
statusResponse = {
|
||||
status: 'failed',
|
||||
message: apiError?.getErrorMessage() || t('unexpected_error'),
|
||||
};
|
||||
showErrorModal(apiError as APIError);
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: t('unexpected_error'),
|
||||
});
|
||||
statusResponse = { status: 'failed', message: t('unexpected_error') };
|
||||
}
|
||||
setLoading(false);
|
||||
logEvent('Alert: Test notification', {
|
||||
@@ -760,7 +772,7 @@ function FormAlertRules({
|
||||
queryType: currentQuery.queryType,
|
||||
link: url,
|
||||
});
|
||||
window.open(url, '_blank');
|
||||
openInNewTab(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
import { QueryData } from 'types/api/widgets/getQuery';
|
||||
@@ -100,7 +102,7 @@ export const saveLegendEntriesToLocalStorage = ({
|
||||
|
||||
try {
|
||||
existingEntries = JSON.parse(
|
||||
localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) || '[]',
|
||||
getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) || '[]',
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error parsing LEGEND_GRAPH from local storage', error);
|
||||
@@ -115,7 +117,7 @@ export const saveLegendEntriesToLocalStorage = ({
|
||||
}
|
||||
|
||||
try {
|
||||
localStorage.setItem(
|
||||
setLocalStorageKey(
|
||||
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
|
||||
JSON.stringify(existingEntries),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import type { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import { NavigateToExplorerProps } from 'components/CeleryTask/useNavigateToExplorer';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -44,8 +45,8 @@ export const getLocalStorageGraphVisibilityState = ({
|
||||
],
|
||||
};
|
||||
|
||||
if (localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
|
||||
const legendGraphFromLocalStore = localStorage.getItem(
|
||||
if (getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
|
||||
const legendGraphFromLocalStore = getLocalStorageKey(
|
||||
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
|
||||
);
|
||||
let legendFromLocalStore: {
|
||||
@@ -94,8 +95,8 @@ export const getGraphVisibilityStateOnDataChange = ({
|
||||
graphVisibilityStates: Array(options.series.length).fill(true),
|
||||
legendEntry: showAllDataSet(options),
|
||||
};
|
||||
if (localStorage.getItem(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
|
||||
const legendGraphFromLocalStore = localStorage.getItem(
|
||||
if (getLocalStorageKey(LOCALSTORAGE.GRAPH_VISIBILITY_STATES) !== null) {
|
||||
const legendGraphFromLocalStore = getLocalStorageKey(
|
||||
LOCALSTORAGE.GRAPH_VISIBILITY_STATES,
|
||||
);
|
||||
let legendFromLocalStore: {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { Button, Skeleton, Tag } from 'antd';
|
||||
import getAll from 'api/alerts/getAll';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useListRules } from 'api/generated/services/rules';
|
||||
import type { RuletypesRuleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
@@ -11,7 +11,7 @@ import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/map
|
||||
import { ArrowRight, ArrowUpRight, Plus } from 'lucide-react';
|
||||
import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { toCompositeMetricQuery } from 'types/api/alerts/convert';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import beaconUrl from '@/assets/Icons/beacon.svg';
|
||||
@@ -28,23 +28,22 @@ export default function AlertRules({
|
||||
const { user } = useAppContext();
|
||||
const [rulesExist, setRulesExist] = useState(false);
|
||||
|
||||
const [sortedAlertRules, setSortedAlertRules] = useState<RuletypesRuleDTO[]>(
|
||||
[],
|
||||
);
|
||||
const [sortedAlertRules, setSortedAlertRules] = useState<GettableAlert[]>([]);
|
||||
|
||||
const location = useLocation();
|
||||
const params = new URLSearchParams(location.search);
|
||||
|
||||
// Fetch Alerts
|
||||
const { data: alerts, isError, isLoading } = useListRules({
|
||||
query: { cacheTime: 0 },
|
||||
const { data: alerts, isError, isLoading } = useQuery('allAlerts', {
|
||||
queryFn: getAll,
|
||||
cacheTime: 0,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const rules = alerts?.data ?? [];
|
||||
const rules = alerts?.payload || [];
|
||||
setRulesExist(rules.length > 0);
|
||||
|
||||
const sortedRules = [...rules].sort((a, b) => {
|
||||
const sortedRules = rules.sort((a, b) => {
|
||||
// First, prioritize firing alerts
|
||||
if (a.state === 'firing' && b.state !== 'firing') {
|
||||
return -1;
|
||||
@@ -53,10 +52,10 @@ export default function AlertRules({
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Then sort by updatedAt timestamp
|
||||
return (
|
||||
new Date(b.updatedAt ?? 0).getTime() - new Date(a.updatedAt ?? 0).getTime()
|
||||
);
|
||||
// Then sort by updateAt timestamp
|
||||
const aUpdateAt = new Date(a.updateAt).getTime();
|
||||
const bUpdateAt = new Date(b.updateAt).getTime();
|
||||
return bUpdateAt - aUpdateAt;
|
||||
});
|
||||
|
||||
if (sortedRules.length > 0 && !loadingUserPreferences) {
|
||||
@@ -119,27 +118,22 @@ export default function AlertRules({
|
||||
</div>
|
||||
);
|
||||
|
||||
const onEditHandler = (record: RuletypesRuleDTO) => (): void => {
|
||||
const onEditHandler = (record: GettableAlert) => (): void => {
|
||||
logEvent('Homepage: Alert clicked', {
|
||||
ruleId: record.id,
|
||||
ruleName: record.alert,
|
||||
ruleState: record.state,
|
||||
});
|
||||
|
||||
const compositeQuery = mapQueryDataFromApi(
|
||||
toCompositeMetricQuery(record.condition.compositeQuery),
|
||||
);
|
||||
const compositeQuery = mapQueryDataFromApi(record.condition.compositeQuery);
|
||||
params.set(
|
||||
QueryParams.compositeQuery,
|
||||
encodeURIComponent(JSON.stringify(compositeQuery)),
|
||||
);
|
||||
|
||||
const panelType = record.condition.compositeQuery.panelType;
|
||||
if (panelType) {
|
||||
params.set(QueryParams.panelTypes, panelType);
|
||||
}
|
||||
params.set(QueryParams.panelTypes, record.condition.compositeQuery.panelType);
|
||||
|
||||
params.set(QueryParams.ruleId, record.id);
|
||||
params.set(QueryParams.ruleId, record.id.toString());
|
||||
|
||||
history.push(`${ROUTES.ALERT_OVERVIEW}?${params.toString()}`);
|
||||
};
|
||||
@@ -175,9 +169,9 @@ export default function AlertRules({
|
||||
<div className="alert-rule-item-description home-data-item-tag">
|
||||
<Tag color={rule?.labels?.severity}>{rule?.labels?.severity}</Tag>
|
||||
|
||||
{rule.state === 'firing' && (
|
||||
{rule?.state === 'firing' && (
|
||||
<Tag color="red" className="firing-tag">
|
||||
{rule.state}
|
||||
{rule?.state}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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 { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import dialsUrl from '@/assets/Icons/dials.svg';
|
||||
|
||||
@@ -114,7 +115,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 { openInNewTab } 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');
|
||||
openInNewTab(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 { openInNewTab } 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',
|
||||
);
|
||||
openInNewTab(item.docsLink || '');
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -119,7 +116,7 @@ function HomeChecklist({
|
||||
step: item.id,
|
||||
});
|
||||
|
||||
window?.open(item.docsLink, '_blank', 'noopener noreferrer');
|
||||
openInNewTab(item.docsLink ?? '');
|
||||
}}
|
||||
>
|
||||
<BookOpenText size={16} />
|
||||
|
||||
@@ -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 { openInNewTab } 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',
|
||||
);
|
||||
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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 { openInNewTab } 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',
|
||||
);
|
||||
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
LogsAggregatorOperator,
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { filterDuplicateFilters } from '../commonUtils';
|
||||
@@ -569,10 +570,7 @@ function K8sBaseDetails<T>({
|
||||
|
||||
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,
|
||||
@@ -591,10 +589,7 @@ function K8sBaseDetails<T>({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
CloudintegrationtypesServiceDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
import './ServiceDashboards.styles.scss';
|
||||
|
||||
@@ -44,7 +45,11 @@ function ServiceDashboards({
|
||||
return;
|
||||
}
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(dashboardUrl, '_blank', 'noopener,noreferrer');
|
||||
window.open(
|
||||
withBasePath(dashboardUrl),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
return;
|
||||
}
|
||||
safeNavigate(dashboardUrl);
|
||||
@@ -54,7 +59,11 @@ function ServiceDashboards({
|
||||
return;
|
||||
}
|
||||
if (event.button === 1) {
|
||||
window.open(dashboardUrl, '_blank', 'noopener,noreferrer');
|
||||
window.open(
|
||||
withBasePath(dashboardUrl),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event): void => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
interface AlertInfoCardProps {
|
||||
header: string;
|
||||
@@ -19,7 +20,7 @@ function AlertInfoCard({
|
||||
className="alert-info-card"
|
||||
onClick={(): void => {
|
||||
onClick();
|
||||
window.open(link, '_blank');
|
||||
openInNewTab(link);
|
||||
}}
|
||||
>
|
||||
<div className="alert-card-text">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ArrowRightOutlined, PlayCircleFilled } from '@ant-design/icons';
|
||||
import { Flex, Typography } from 'antd';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
interface InfoLinkTextProps {
|
||||
infoText: string;
|
||||
@@ -20,7 +21,7 @@ function InfoLinkText({
|
||||
<Flex
|
||||
onClick={(): void => {
|
||||
onClick();
|
||||
window.open(link, '_blank');
|
||||
openInNewTab(link);
|
||||
}}
|
||||
className="info-link-container"
|
||||
>
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
import { Dispatch, SetStateAction, useState } from 'react';
|
||||
import type { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { deleteRuleByID } from 'api/generated/services/rules';
|
||||
import type {
|
||||
RenderErrorResponseDTO,
|
||||
RuletypesRuleDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import deleteAlerts from 'api/alerts/delete';
|
||||
import { State } from 'hooks/useFetch';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { PayloadProps as DeleteAlertPayloadProps } from 'types/api/alerts/delete';
|
||||
import APIError from 'types/api/error';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
|
||||
import { ColumnButton } from './styles';
|
||||
|
||||
@@ -29,31 +22,48 @@ function DeleteAlert({
|
||||
payload: undefined,
|
||||
});
|
||||
|
||||
const { showErrorModal } = useErrorModal();
|
||||
const defaultErrorMessage = 'Something went wrong';
|
||||
|
||||
const onDeleteHandler = async (id: string): Promise<void> => {
|
||||
try {
|
||||
await deleteRuleByID({ id });
|
||||
|
||||
setData((state) => state.filter((alert) => alert.id !== id));
|
||||
|
||||
setDeleteAlertState((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
}));
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
const response = await deleteAlerts({
|
||||
id,
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setData((state) => state.filter((alert) => alert.id !== id));
|
||||
|
||||
setDeleteAlertState((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
payload: response.payload,
|
||||
}));
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
});
|
||||
} else {
|
||||
setDeleteAlertState((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
error: true,
|
||||
errorMessage: response.error || defaultErrorMessage,
|
||||
}));
|
||||
|
||||
notifications.error({
|
||||
message: response.error || defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setDeleteAlertState((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
error: true,
|
||||
errorMessage: defaultErrorMessage,
|
||||
}));
|
||||
|
||||
showErrorModal(
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) as APIError,
|
||||
);
|
||||
notifications.error({
|
||||
message: defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,8 +88,8 @@ function DeleteAlert({
|
||||
}
|
||||
|
||||
interface DeleteAlertProps {
|
||||
id: string;
|
||||
setData: Dispatch<SetStateAction<RuletypesRuleDTO[]>>;
|
||||
id: GettableAlert['id'];
|
||||
setData: Dispatch<SetStateAction<GettableAlert[]>>;
|
||||
notifications: NotificationInstance;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,16 +4,8 @@ import { UseQueryResult } from 'react-query';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Button, Flex, Input, Typography } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import saveAlertApi from 'api/alerts/save';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { createRule } from 'api/generated/services/rules';
|
||||
import type {
|
||||
ListRules200,
|
||||
RenderErrorResponseDTO,
|
||||
RuletypesRuleDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import type { ErrorType } from 'api/generatedAPIInstance';
|
||||
import { AxiosError } from 'axios';
|
||||
import DropDown from 'components/DropDown/DropDown';
|
||||
import {
|
||||
DynamicColumnsKey,
|
||||
@@ -35,9 +27,9 @@ import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { toCompositeMetricQuery } from 'types/api/alerts/convert';
|
||||
import APIError from 'types/api/error';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
|
||||
import DeleteAlert from './DeleteAlert';
|
||||
@@ -66,7 +58,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
const paginationParam = params.get('page');
|
||||
const searchParams = params.get('search');
|
||||
const [searchString, setSearchString] = useState<string>(searchParams || '');
|
||||
const [data, setData] = useState<RuletypesRuleDTO[]>(() => {
|
||||
const [data, setData] = useState<GettableAlert[]>(() => {
|
||||
const value = searchString.toLowerCase();
|
||||
const filteredData = filterAlerts(allAlertRules, value);
|
||||
return filteredData || [];
|
||||
@@ -78,7 +70,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
? orderQueryParam
|
||||
: null;
|
||||
|
||||
const { sortedInfo, handleChange } = useSortableTable<RuletypesRuleDTO>(
|
||||
const { sortedInfo, handleChange } = useSortableTable<GettableAlert>(
|
||||
sortingOrder,
|
||||
orderColumnParam || '',
|
||||
searchString,
|
||||
@@ -91,7 +83,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
const { data: refetchData, status } = await refetch();
|
||||
if (status === 'success') {
|
||||
const value = searchString.toLowerCase();
|
||||
const filteredData = filterAlerts(refetchData?.data ?? [], value);
|
||||
const filteredData = filterAlerts(refetchData.payload || [], value);
|
||||
setData(filteredData || []);
|
||||
}
|
||||
if (status === 'error') {
|
||||
@@ -102,7 +94,11 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
})();
|
||||
}, 30000);
|
||||
|
||||
const { showErrorModal } = useErrorModal();
|
||||
const handleError = useCallback((): void => {
|
||||
notificationsApi.error({
|
||||
message: t('something_went_wrong'),
|
||||
});
|
||||
}, [notificationsApi, t]);
|
||||
|
||||
const onClickNewAlertHandler = useCallback(
|
||||
(e: React.MouseEvent): void => {
|
||||
@@ -119,24 +115,21 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
);
|
||||
|
||||
const onEditHandler = (
|
||||
record: RuletypesRuleDTO,
|
||||
record: GettableAlert,
|
||||
options?: { newTab?: boolean },
|
||||
): void => {
|
||||
const compositeQuery = sanitizeDefaultAlertQuery(
|
||||
mapQueryDataFromApi(toCompositeMetricQuery(record.condition.compositeQuery)),
|
||||
record.alertType,
|
||||
mapQueryDataFromApi(record.condition.compositeQuery),
|
||||
record.alertType as AlertTypes,
|
||||
);
|
||||
params.set(
|
||||
QueryParams.compositeQuery,
|
||||
encodeURIComponent(JSON.stringify(compositeQuery)),
|
||||
);
|
||||
|
||||
const panelType = record.condition.compositeQuery.panelType;
|
||||
if (panelType) {
|
||||
params.set(QueryParams.panelTypes, panelType);
|
||||
}
|
||||
params.set(QueryParams.panelTypes, record.condition.compositeQuery.panelType);
|
||||
|
||||
params.set(QueryParams.ruleId, record.id);
|
||||
params.set(QueryParams.ruleId, record.id.toString());
|
||||
|
||||
setEditLoader(false);
|
||||
|
||||
@@ -146,41 +139,47 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
};
|
||||
|
||||
const onCloneHandler = (
|
||||
originalAlert: RuletypesRuleDTO,
|
||||
originalAlert: GettableAlert,
|
||||
) => async (): Promise<void> => {
|
||||
const copyAlert: RuletypesRuleDTO = {
|
||||
const copyAlert = {
|
||||
...originalAlert,
|
||||
alert: `${originalAlert.alert} - Copy`,
|
||||
alert: originalAlert.alert.concat(' - Copy'),
|
||||
};
|
||||
const apiReq = { data: copyAlert };
|
||||
|
||||
try {
|
||||
setCloneLoader(true);
|
||||
await createRule(copyAlert);
|
||||
const response = await saveAlertApi(apiReq);
|
||||
|
||||
notificationsApi.success({
|
||||
message: 'Success',
|
||||
description: 'Alert cloned successfully',
|
||||
});
|
||||
if (response.statusCode === 200) {
|
||||
notificationsApi.success({
|
||||
message: 'Success',
|
||||
description: 'Alert cloned successfully',
|
||||
});
|
||||
|
||||
const { data: refetchData, status } = await refetch();
|
||||
const rules = refetchData?.data;
|
||||
if (status === 'success' && rules) {
|
||||
setData(rules);
|
||||
setTimeout(() => {
|
||||
const clonedAlert = rules[rules.length - 1];
|
||||
params.set(QueryParams.ruleId, String(clonedAlert.id));
|
||||
safeNavigate(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
|
||||
}, 2000);
|
||||
}
|
||||
if (status === 'error') {
|
||||
const { data: refetchData, status } = await refetch();
|
||||
if (status === 'success' && refetchData.payload) {
|
||||
setData(refetchData.payload || []);
|
||||
setTimeout(() => {
|
||||
const clonedAlert = refetchData.payload[refetchData.payload.length - 1];
|
||||
params.set(QueryParams.ruleId, String(clonedAlert.id));
|
||||
safeNavigate(`${ROUTES.EDIT_ALERTS}?${params.toString()}`);
|
||||
}, 2000);
|
||||
}
|
||||
if (status === 'error') {
|
||||
notificationsApi.error({
|
||||
message: t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
notificationsApi.error({
|
||||
message: t('something_went_wrong'),
|
||||
message: 'Error',
|
||||
description: response.error || t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorModal(
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) as APIError,
|
||||
);
|
||||
handleError();
|
||||
console.error(error);
|
||||
} finally {
|
||||
setCloneLoader(false);
|
||||
}
|
||||
@@ -193,16 +192,16 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
setData(filteredData);
|
||||
});
|
||||
|
||||
const dynamicColumns: ColumnsType<RuletypesRuleDTO> = [
|
||||
const dynamicColumns: ColumnsType<GettableAlert> = [
|
||||
{
|
||||
title: 'Created At',
|
||||
dataIndex: 'createdAt',
|
||||
dataIndex: 'createAt',
|
||||
width: 80,
|
||||
key: DynamicColumnsKey.CreatedAt,
|
||||
align: 'center',
|
||||
sorter: (a: RuletypesRuleDTO, b: RuletypesRuleDTO): number => {
|
||||
const prev = a.createdAt ? new Date(a.createdAt).getTime() : 0;
|
||||
const next = b.createdAt ? new Date(b.createdAt).getTime() : 0;
|
||||
sorter: (a: GettableAlert, b: GettableAlert): number => {
|
||||
const prev = new Date(a.createAt).getTime();
|
||||
const next = new Date(b.createAt).getTime();
|
||||
|
||||
return prev - next;
|
||||
},
|
||||
@@ -214,20 +213,20 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
},
|
||||
{
|
||||
title: 'Created By',
|
||||
dataIndex: 'createdBy',
|
||||
dataIndex: 'createBy',
|
||||
width: 80,
|
||||
key: DynamicColumnsKey.CreatedBy,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: 'Updated At',
|
||||
dataIndex: 'updatedAt',
|
||||
dataIndex: 'updateAt',
|
||||
width: 80,
|
||||
key: DynamicColumnsKey.UpdatedAt,
|
||||
align: 'center',
|
||||
sorter: (a: RuletypesRuleDTO, b: RuletypesRuleDTO): number => {
|
||||
const prev = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
|
||||
const next = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
|
||||
sorter: (a: GettableAlert, b: GettableAlert): number => {
|
||||
const prev = new Date(a.updateAt).getTime();
|
||||
const next = new Date(b.updateAt).getTime();
|
||||
|
||||
return prev - next;
|
||||
},
|
||||
@@ -239,14 +238,14 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
},
|
||||
{
|
||||
title: 'Updated By',
|
||||
dataIndex: 'updatedBy',
|
||||
dataIndex: 'updateBy',
|
||||
width: 80,
|
||||
key: DynamicColumnsKey.UpdatedBy,
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
const columns: ColumnsType<RuletypesRuleDTO> = [
|
||||
const columns: ColumnsType<GettableAlert> = [
|
||||
{
|
||||
title: 'Status',
|
||||
dataIndex: 'state',
|
||||
@@ -323,7 +322,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
dataIndex: 'id',
|
||||
key: 'action',
|
||||
width: 10,
|
||||
render: (id: RuletypesRuleDTO['id'], record): JSX.Element => (
|
||||
render: (id: GettableAlert['id'], record): JSX.Element => (
|
||||
<div data-testid="alert-actions">
|
||||
<DropDown
|
||||
onDropDownItemClick={(item): void =>
|
||||
@@ -332,9 +331,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
element={[
|
||||
<ToggleAlertState
|
||||
key="1"
|
||||
disabled={record.disabled ?? false}
|
||||
disabled={record.disabled}
|
||||
setData={setData}
|
||||
id={id ?? ''}
|
||||
id={id}
|
||||
/>,
|
||||
<ColumnButton
|
||||
key="2"
|
||||
@@ -366,7 +365,7 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
key="4"
|
||||
notifications={notificationsApi}
|
||||
setData={setData}
|
||||
id={id ?? ''}
|
||||
id={id}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
@@ -421,10 +420,9 @@ function ListAlert({ allAlertRules, refetch }: ListAlertProps): JSX.Element {
|
||||
}
|
||||
|
||||
interface ListAlertProps {
|
||||
allAlertRules: RuletypesRuleDTO[];
|
||||
allAlertRules: GettableAlert[];
|
||||
refetch: UseQueryResult<
|
||||
ListRules200,
|
||||
ErrorType<RenderErrorResponseDTO>
|
||||
ErrorResponse | SuccessResponse<GettableAlert[]>
|
||||
>['refetch'];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Tag } from 'antd';
|
||||
import type { RuletypesRuleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
|
||||
function Status({ status }: StatusProps): JSX.Element {
|
||||
switch (status) {
|
||||
@@ -26,7 +26,7 @@ function Status({ status }: StatusProps): JSX.Element {
|
||||
}
|
||||
|
||||
interface StatusProps {
|
||||
status: RuletypesRuleDTO['state'];
|
||||
status: GettableAlert['state'];
|
||||
}
|
||||
|
||||
export default Status;
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import { Dispatch, SetStateAction, useState } from 'react';
|
||||
import { patchRulePartial } from 'api/alerts/patchRulePartial';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import type {
|
||||
RenderErrorResponseDTO,
|
||||
RuletypesRuleDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import patchAlert from 'api/alerts/patch';
|
||||
import { State } from 'hooks/useFetch';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
import { PayloadProps as PatchPayloadProps } from 'types/api/alerts/patch';
|
||||
|
||||
import { ColumnButton } from './styles';
|
||||
|
||||
@@ -18,7 +12,7 @@ function ToggleAlertState({
|
||||
disabled,
|
||||
setData,
|
||||
}: ToggleAlertStateProps): JSX.Element {
|
||||
const [apiStatus, setAPIStatus] = useState<State<RuletypesRuleDTO>>({
|
||||
const [apiStatus, setAPIStatus] = useState<State<PatchPayloadProps>>({
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
loading: false,
|
||||
@@ -27,7 +21,8 @@ function ToggleAlertState({
|
||||
});
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const defaultErrorMessage = 'Something went wrong';
|
||||
|
||||
const onToggleHandler = async (
|
||||
id: string,
|
||||
@@ -39,40 +34,58 @@ function ToggleAlertState({
|
||||
loading: true,
|
||||
}));
|
||||
|
||||
const response = await patchRulePartial(id, { disabled });
|
||||
const { data: updatedRule } = response;
|
||||
|
||||
setData((state) =>
|
||||
state.map((alert) => {
|
||||
if (alert.id === id) {
|
||||
return {
|
||||
...alert,
|
||||
disabled: updatedRule.disabled,
|
||||
state: updatedRule.state,
|
||||
};
|
||||
}
|
||||
return alert;
|
||||
}),
|
||||
);
|
||||
|
||||
setAPIStatus((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
payload: updatedRule,
|
||||
}));
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
const response = await patchAlert({
|
||||
id,
|
||||
data: {
|
||||
disabled,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.statusCode === 200) {
|
||||
setData((state) =>
|
||||
state.map((alert) => {
|
||||
if (alert.id === id) {
|
||||
return {
|
||||
...alert,
|
||||
disabled: response.payload.disabled,
|
||||
state: response.payload.state,
|
||||
};
|
||||
}
|
||||
return alert;
|
||||
}),
|
||||
);
|
||||
|
||||
setAPIStatus((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
payload: response.payload,
|
||||
}));
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
});
|
||||
} else {
|
||||
setAPIStatus((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
error: true,
|
||||
errorMessage: response.error || defaultErrorMessage,
|
||||
}));
|
||||
|
||||
notifications.error({
|
||||
message: response.error || defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
setAPIStatus((state) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
error: true,
|
||||
errorMessage: defaultErrorMessage,
|
||||
}));
|
||||
|
||||
showErrorModal(
|
||||
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) as APIError,
|
||||
);
|
||||
notifications.error({
|
||||
message: defaultErrorMessage,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -89,9 +102,9 @@ function ToggleAlertState({
|
||||
}
|
||||
|
||||
interface ToggleAlertStateProps {
|
||||
id: string;
|
||||
id: GettableAlert['id'];
|
||||
disabled: boolean;
|
||||
setData: Dispatch<SetStateAction<RuletypesRuleDTO[]>>;
|
||||
setData: Dispatch<SetStateAction<GettableAlert[]>>;
|
||||
}
|
||||
|
||||
export default ToggleAlertState;
|
||||
|
||||
@@ -1,69 +1,52 @@
|
||||
import type {
|
||||
RuletypesAlertStateDTO,
|
||||
RuletypesCompareOperatorDTO,
|
||||
RuletypesMatchTypeDTO,
|
||||
RuletypesPanelTypeDTO,
|
||||
RuletypesQueryTypeDTO,
|
||||
RuletypesRuleDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
|
||||
import { filterAlerts } from '../utils';
|
||||
|
||||
describe('filterAlerts', () => {
|
||||
const mockAlertBase: Partial<RuletypesRuleDTO> = {
|
||||
state: 'active' as RuletypesAlertStateDTO,
|
||||
const mockAlertBase: Partial<GettableAlert> = {
|
||||
state: 'active',
|
||||
disabled: false,
|
||||
createdAt: new Date('2024-01-01T00:00:00Z'),
|
||||
createdBy: 'test-user',
|
||||
updatedAt: new Date('2024-01-01T00:00:00Z'),
|
||||
updatedBy: 'test-user',
|
||||
createAt: '2024-01-01T00:00:00Z',
|
||||
createBy: 'test-user',
|
||||
updateAt: '2024-01-01T00:00:00Z',
|
||||
updateBy: 'test-user',
|
||||
version: '1',
|
||||
condition: {
|
||||
compositeQuery: {
|
||||
queries: [],
|
||||
panelType: 'graph' as RuletypesPanelTypeDTO,
|
||||
queryType: 'builder' as RuletypesQueryTypeDTO,
|
||||
},
|
||||
matchType: 'at_least_once' as RuletypesMatchTypeDTO,
|
||||
op: 'above' as RuletypesCompareOperatorDTO,
|
||||
},
|
||||
ruleType: 'threshold_rule' as RuletypesRuleDTO['ruleType'],
|
||||
};
|
||||
|
||||
const mockAlerts: RuletypesRuleDTO[] = [
|
||||
const mockAlerts: GettableAlert[] = [
|
||||
{
|
||||
...mockAlertBase,
|
||||
id: '1',
|
||||
alert: 'High CPU Usage',
|
||||
alertType: 'METRIC_BASED_ALERT',
|
||||
alertType: 'metrics',
|
||||
labels: {
|
||||
severity: 'warning',
|
||||
status: 'ok',
|
||||
environment: 'production',
|
||||
},
|
||||
} as RuletypesRuleDTO,
|
||||
} as GettableAlert,
|
||||
{
|
||||
...mockAlertBase,
|
||||
id: '2',
|
||||
alert: 'Memory Leak Detected',
|
||||
alertType: 'METRIC_BASED_ALERT',
|
||||
alertType: 'metrics',
|
||||
labels: {
|
||||
severity: 'critical',
|
||||
status: 'firing',
|
||||
environment: 'staging',
|
||||
},
|
||||
} as RuletypesRuleDTO,
|
||||
} as GettableAlert,
|
||||
{
|
||||
...mockAlertBase,
|
||||
id: '3',
|
||||
alert: 'Database Connection Error',
|
||||
alertType: 'METRIC_BASED_ALERT',
|
||||
alertType: 'metrics',
|
||||
labels: {
|
||||
severity: 'error',
|
||||
status: 'pending',
|
||||
environment: 'production',
|
||||
},
|
||||
} as RuletypesRuleDTO,
|
||||
} as GettableAlert,
|
||||
];
|
||||
|
||||
it('should return all alerts when filter is empty', () => {
|
||||
@@ -114,14 +97,14 @@ describe('filterAlerts', () => {
|
||||
});
|
||||
|
||||
it('should handle alerts with missing labels', () => {
|
||||
const alertsWithMissingLabels: RuletypesRuleDTO[] = [
|
||||
const alertsWithMissingLabels: GettableAlert[] = [
|
||||
{
|
||||
...mockAlertBase,
|
||||
id: '4',
|
||||
alert: 'Test Alert',
|
||||
alertType: 'METRIC_BASED_ALERT',
|
||||
alertType: 'metrics',
|
||||
labels: undefined,
|
||||
} as RuletypesRuleDTO,
|
||||
} as GettableAlert,
|
||||
];
|
||||
const result = filterAlerts(alertsWithMissingLabels, 'test');
|
||||
expect(result).toHaveLength(1);
|
||||
@@ -129,16 +112,16 @@ describe('filterAlerts', () => {
|
||||
});
|
||||
|
||||
it('should handle alerts with missing alert name', () => {
|
||||
const alertsWithMissingName: RuletypesRuleDTO[] = [
|
||||
const alertsWithMissingName: GettableAlert[] = [
|
||||
{
|
||||
...mockAlertBase,
|
||||
id: '5',
|
||||
alert: '',
|
||||
alertType: 'METRIC_BASED_ALERT',
|
||||
alertType: 'metrics',
|
||||
labels: {
|
||||
severity: 'warning',
|
||||
},
|
||||
} as RuletypesRuleDTO,
|
||||
} as GettableAlert,
|
||||
];
|
||||
const result = filterAlerts(alertsWithMissingName, 'warning');
|
||||
expect(result).toHaveLength(1);
|
||||
|
||||
@@ -1,66 +1,78 @@
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Space } from 'antd';
|
||||
import getAll from 'api/alerts/getAll';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import { useListRules } from 'api/generated/services/rules';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { isUndefined } from 'lodash-es';
|
||||
|
||||
import { AlertsEmptyState } from './AlertsEmptyState/AlertsEmptyState';
|
||||
import ListAlert from './ListAlert';
|
||||
|
||||
function ListAlertRules(): JSX.Element {
|
||||
const { t } = useTranslation('common');
|
||||
const { data, isError, isLoading, refetch, error } = useListRules({
|
||||
query: { cacheTime: 0 },
|
||||
const { data, isError, isLoading, refetch, status } = useQuery('allAlerts', {
|
||||
queryFn: getAll,
|
||||
cacheTime: 0,
|
||||
});
|
||||
|
||||
const rules = data?.data ?? [];
|
||||
const hasLoaded = !isLoading && data !== undefined;
|
||||
const logEventCalledRef = useRef(false);
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const apiError = useMemo(
|
||||
() => convertToApiError(error as AxiosError<RenderErrorResponseDTO> | null),
|
||||
[error],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!logEventCalledRef.current && hasLoaded) {
|
||||
if (!logEventCalledRef.current && !isUndefined(data?.payload)) {
|
||||
logEvent('Alert: List page visited', {
|
||||
number: rules.length,
|
||||
number: data?.payload?.length,
|
||||
});
|
||||
logEventCalledRef.current = true;
|
||||
}
|
||||
}, [hasLoaded, rules.length]);
|
||||
}, [data?.payload]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isError) {
|
||||
if (status === 'error' || (status === 'success' && data.statusCode >= 400)) {
|
||||
notifications.error({
|
||||
message: apiError?.getErrorMessage() || t('something_went_wrong'),
|
||||
message: data?.error || t('something_went_wrong'),
|
||||
});
|
||||
}
|
||||
}, [isError, apiError, t, notifications]);
|
||||
}, [data?.error, data?.statusCode, status, t, notifications]);
|
||||
|
||||
// api failed to load the data
|
||||
if (isError) {
|
||||
return <div>{apiError?.getErrorMessage() || t('something_went_wrong')}</div>;
|
||||
return <div>{data?.error || t('something_went_wrong')}</div>;
|
||||
}
|
||||
|
||||
if (isLoading || !data) {
|
||||
return <Spinner height="75vh" tip="Loading Rules..." />;
|
||||
// api is successful but error is present
|
||||
if (status === 'success' && data.statusCode >= 400) {
|
||||
return (
|
||||
<ListAlert
|
||||
{...{
|
||||
allAlertRules: [],
|
||||
refetch,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (rules.length === 0) {
|
||||
if (status === 'success' && !data.payload?.length) {
|
||||
return <AlertsEmptyState />;
|
||||
}
|
||||
|
||||
// in case of loading
|
||||
if (isLoading || !data?.payload) {
|
||||
return <Spinner height="75vh" tip="Loading Rules..." />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Space direction="vertical" size="large" style={{ width: '100%' }}>
|
||||
<ListAlert allAlertRules={rules} refetch={refetch} />
|
||||
<ListAlert
|
||||
{...{
|
||||
allAlertRules: data.payload,
|
||||
refetch,
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import type { RuletypesRuleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { dataSourceForAlertType } from 'constants/alerts';
|
||||
import { ALERTS_DATA_SOURCE_MAP } from 'constants/alerts';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { GettableAlert } from 'types/api/alerts/get';
|
||||
|
||||
export const filterAlerts = (
|
||||
allAlertRules: RuletypesRuleDTO[],
|
||||
allAlertRules: GettableAlert[],
|
||||
filter: string,
|
||||
): RuletypesRuleDTO[] => {
|
||||
): GettableAlert[] => {
|
||||
if (!filter.trim()) {
|
||||
return allAlertRules;
|
||||
}
|
||||
|
||||
const value = filter.trim().toLowerCase();
|
||||
return allAlertRules.filter((alert) => {
|
||||
const alertName = alert.alert.toLowerCase();
|
||||
const alertName = alert.alert?.toLowerCase();
|
||||
const severity = alert.labels?.severity?.toLowerCase();
|
||||
|
||||
// Create a string of all label keys and values for searching
|
||||
@@ -22,7 +23,7 @@ export const filterAlerts = (
|
||||
.toLowerCase();
|
||||
|
||||
return (
|
||||
alertName.includes(value) ||
|
||||
alertName?.includes(value) ||
|
||||
severity?.includes(value) ||
|
||||
labelSearchString.includes(value)
|
||||
);
|
||||
@@ -31,7 +32,7 @@ export const filterAlerts = (
|
||||
|
||||
export const alertActionLogEvent = (
|
||||
action: string,
|
||||
record: RuletypesRuleDTO,
|
||||
record: GettableAlert,
|
||||
): void => {
|
||||
let actionValue = '';
|
||||
switch (action) {
|
||||
@@ -51,9 +52,9 @@ export const alertActionLogEvent = (
|
||||
break;
|
||||
}
|
||||
logEvent('Alert: Action', {
|
||||
ruleId: record.id,
|
||||
dataSource: dataSourceForAlertType(record.alertType),
|
||||
name: record.alert,
|
||||
ruleId: record?.id,
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[record.alertType as AlertTypes],
|
||||
name: record?.alert,
|
||||
action: actionValue,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import type { TableProps } from 'antd/lib';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import createDashboard from 'api/v1/dashboards/create';
|
||||
import { AxiosError } from 'axios';
|
||||
@@ -83,6 +85,8 @@ import {
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import awwSnapUrl from '@/assets/Icons/awwSnap.svg';
|
||||
import dashboardsUrl from '@/assets/Icons/dashboards.svg';
|
||||
@@ -145,7 +149,7 @@ function DashboardsList(): JSX.Element {
|
||||
);
|
||||
|
||||
const getLocalStorageDynamicColumns = (): DashboardDynamicColumns => {
|
||||
const dashboardDynamicColumnsString = localStorage.getItem('dashboard');
|
||||
const dashboardDynamicColumnsString = getLocalStorageKey('dashboard');
|
||||
let dashboardDynamicColumns: DashboardDynamicColumns = {
|
||||
createdAt: true,
|
||||
createdBy: true,
|
||||
@@ -159,7 +163,7 @@ function DashboardsList(): JSX.Element {
|
||||
);
|
||||
|
||||
if (isEmpty(tempDashboardDynamicColumns)) {
|
||||
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
|
||||
setLocalStorageKey('dashboard', JSON.stringify(dashboardDynamicColumns));
|
||||
} else {
|
||||
dashboardDynamicColumns = { ...tempDashboardDynamicColumns };
|
||||
}
|
||||
@@ -167,7 +171,7 @@ function DashboardsList(): JSX.Element {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
localStorage.setItem('dashboard', JSON.stringify(dashboardDynamicColumns));
|
||||
setLocalStorageKey('dashboard', JSON.stringify(dashboardDynamicColumns));
|
||||
}
|
||||
|
||||
return dashboardDynamicColumns;
|
||||
@@ -181,7 +185,7 @@ function DashboardsList(): JSX.Element {
|
||||
visibleColumns: DashboardDynamicColumns,
|
||||
): void {
|
||||
try {
|
||||
localStorage.setItem('dashboard', JSON.stringify(visibleColumns));
|
||||
setLocalStorageKey('dashboard', JSON.stringify(visibleColumns));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@@ -457,7 +461,7 @@ function DashboardsList(): JSX.Element {
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
window.open(getLink(), '_blank');
|
||||
openInNewTab(getLink());
|
||||
}}
|
||||
>
|
||||
Open in New Tab
|
||||
@@ -469,7 +473,7 @@ function DashboardsList(): JSX.Element {
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setCopy(`${window.location.origin}${getLink()}`);
|
||||
setCopy(getAbsoluteUrl(getLink()));
|
||||
}}
|
||||
>
|
||||
Copy Link
|
||||
|
||||
@@ -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 { withBasePath } from 'utils/basePath';
|
||||
|
||||
import { useContextLogData } from './useContextLogData';
|
||||
|
||||
@@ -116,7 +117,7 @@ function ContextLogRenderer({
|
||||
);
|
||||
|
||||
const link = `${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`;
|
||||
window.open(link, '_blank', 'noopener,noreferrer');
|
||||
window.open(withBasePath(link), '_blank', 'noopener,noreferrer');
|
||||
},
|
||||
[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);
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@ function Login(): JSX.Element {
|
||||
if (isCallbackAuthN) {
|
||||
const url = form.getFieldValue('url');
|
||||
|
||||
// eslint-disable-next-line rulesdir/no-raw-absolute-path
|
||||
window.location.href = url;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import ROUTES from 'constants/routes';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useDragColumns from 'hooks/useDragColumns';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
import { infinityDefaultStyles } from '../InfinityTableView/config';
|
||||
import { TanStackTableStyled } from '../InfinityTableView/styles';
|
||||
@@ -239,7 +240,7 @@ const TanStackTableView = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
||||
urlQuery.delete(QueryParams.activeLogId);
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
urlQuery.set(QueryParams.activeLogId, `"${logId}"`);
|
||||
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
|
||||
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
|
||||
|
||||
setCopy(link);
|
||||
toast.success('Copied to clipboard', { position: 'top-right' });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
import { TopOperationList } from './TopOperationsTable';
|
||||
import { NavigateToTraceProps } from './types';
|
||||
@@ -37,7 +38,7 @@ export const navigateToTrace = ({
|
||||
}=${JSONCompositeQuery}`;
|
||||
|
||||
if (openInNewTab) {
|
||||
window.open(newTraceExplorerPath, '_blank');
|
||||
window.open(withBasePath(newTraceExplorerPath), '_blank');
|
||||
} 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,10 @@ function DashboardsAndAlertsPopover({
|
||||
<Typography.Link
|
||||
key={dashboard.dashboardId}
|
||||
onClick={(): void => {
|
||||
window.open(
|
||||
openInNewTab(
|
||||
generatePath(ROUTES.DASHBOARD, {
|
||||
dashboardId: dashboard.dashboardId,
|
||||
}),
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
className="dashboards-popover-content-item"
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import useContextVariables from 'hooks/dashboard/useContextVariables';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { ContextLinkProps, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
import VariablesDropdown from './VariablesDropdown';
|
||||
|
||||
@@ -84,7 +85,7 @@ function UpdateContextLinks({
|
||||
);
|
||||
|
||||
// Function to get current domain
|
||||
const getCurrentDomain = (): string => window.location.origin;
|
||||
const getCurrentDomain = (): string => getBaseUrl();
|
||||
|
||||
// Function to handle variable selection from dropdown
|
||||
const handleVariableSelect = (
|
||||
|
||||
@@ -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 { openInNewTab } 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');
|
||||
openInNewTab(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE);
|
||||
} else if (dataSource === DataSource.METRICS) {
|
||||
window.open(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE, '_blank');
|
||||
openInNewTab(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE);
|
||||
} else {
|
||||
window.open(`${DOCLINKS.USER_GUIDE}${dataSource}/`, '_blank');
|
||||
openInNewTab(`${DOCLINKS.USER_GUIDE}${dataSource}/`);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { OnboardingQuestionHeader } from '../OnboardingQuestionHeader';
|
||||
@@ -60,7 +61,7 @@ function InviteTeamMembers({
|
||||
email: '',
|
||||
role: '',
|
||||
name: '',
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
id: '',
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DOCS_BASE_URL } from 'constants/app';
|
||||
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { ArrowUpRight, Copy, Info, Key, TriangleAlert } from 'lucide-react';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
import './IngestionDetails.styles.scss';
|
||||
|
||||
@@ -215,7 +216,7 @@ export default function OnboardingIngestionDetails(): JSX.Element {
|
||||
</a>
|
||||
. To create a new one,{' '}
|
||||
<a
|
||||
href="/settings/ingestion-settings"
|
||||
href={withBasePath('/settings/ingestion-settings')}
|
||||
target="_blank"
|
||||
className="learn-more"
|
||||
rel="noreferrer"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useNotifications } from 'hooks/useNotifications';
|
||||
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
|
||||
import { ArrowRight, CheckCircle, Plus, TriangleAlert, X } from 'lucide-react';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import './InviteTeamMembers.styles.scss';
|
||||
@@ -56,7 +57,7 @@ function InviteTeamMembers({
|
||||
email: '',
|
||||
role: 'EDITOR',
|
||||
name: '',
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
id: '',
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import ErrorContent from 'components/ErrorModal/components/ErrorContent';
|
||||
import CopyToClipboard from 'periscope/components/CopyToClipboard';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
import CreateEdit from './CreateEdit/CreateEdit';
|
||||
import SSOEnforcementToggle from './SSOEnforcementToggle';
|
||||
@@ -145,7 +146,7 @@ function AuthDomain(): JSX.Element {
|
||||
return <span className="auth-domain-list-na">N/A</span>;
|
||||
}
|
||||
|
||||
const href = `${window.location.origin}/${relayPath}`;
|
||||
const href = getAbsoluteUrl(`/${relayPath}`);
|
||||
return <CopyToClipboard textToCopy={href} />;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button, Form, FormInstance, Modal } from 'antd';
|
||||
import sendInvite from 'api/v1/invite/create';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
import InviteTeamMembers from '../InviteTeamMembers';
|
||||
import { InviteMemberFormValues } from '../utils';
|
||||
@@ -40,7 +41,7 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
|
||||
email: member.email,
|
||||
name: member?.name,
|
||||
role: member.role,
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
});
|
||||
|
||||
notifications.success({
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import React, { ChangeEvent, useEffect, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Flex, Form, Input, Tooltip, Typography } from 'antd';
|
||||
import getAll from 'api/alerts/getAll';
|
||||
import { useDeleteDowntimeSchedule } from 'api/plannedDowntime/deleteDowntimeSchedule';
|
||||
import {
|
||||
useDeleteDowntimeScheduleByID,
|
||||
useListDowntimeSchedules,
|
||||
} from 'api/generated/services/downtimeschedules';
|
||||
import { useListRules } from 'api/generated/services/rules';
|
||||
import type { RuletypesPlannedMaintenanceDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
DowntimeSchedules,
|
||||
useGetAllDowntimeSchedules,
|
||||
} from 'api/plannedDowntime/getAllDowntimeSchedules';
|
||||
import dayjs from 'dayjs';
|
||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { Search } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
|
||||
import 'dayjs/locale/en';
|
||||
@@ -33,28 +33,28 @@ import './PlannedDowntime.styles.scss';
|
||||
dayjs.locale('en');
|
||||
|
||||
export function PlannedDowntime(): JSX.Element {
|
||||
const { data: alertsData, isError, isLoading } = useListRules({
|
||||
query: { cacheTime: 0 },
|
||||
const { data, isError, isLoading } = useQuery('allAlerts', {
|
||||
queryFn: getAll,
|
||||
cacheTime: 0,
|
||||
});
|
||||
const [isOpen, setIsOpen] = React.useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const { user } = useAppContext();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
const history = useHistory();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const [initialValues, setInitialValues] = useState<
|
||||
Partial<RuletypesPlannedMaintenanceDTO & { editMode: boolean }>
|
||||
Partial<DowntimeSchedules & { editMode: boolean }>
|
||||
>(defautlInitialValues);
|
||||
|
||||
const downtimeSchedules = useListDowntimeSchedules();
|
||||
const downtimeSchedules = useGetAllDowntimeSchedules();
|
||||
const alertOptions = React.useMemo(
|
||||
() =>
|
||||
alertsData?.data?.map((i) => ({
|
||||
data?.payload?.map((i) => ({
|
||||
label: i.alert,
|
||||
value: i.id,
|
||||
})),
|
||||
[alertsData],
|
||||
[data],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -66,7 +66,7 @@ export function PlannedDowntime(): JSX.Element {
|
||||
const [searchValue, setSearchValue] = React.useState<string | number>(
|
||||
urlQuery.get('search') || '',
|
||||
);
|
||||
const [deleteData, setDeleteData] = useState<{ id: string; name: string }>();
|
||||
const [deleteData, setDeleteData] = useState<{ id: number; name: string }>();
|
||||
const [isEditMode, setEditMode] = useState<boolean>(false);
|
||||
|
||||
const updateUrlWithSearch = useDebouncedFn((value) => {
|
||||
@@ -105,13 +105,12 @@ export function PlannedDowntime(): JSX.Element {
|
||||
const {
|
||||
mutateAsync: deleteDowntimeScheduleAsync,
|
||||
isLoading: isDeleteLoading,
|
||||
} = useDeleteDowntimeScheduleByID();
|
||||
} = useDeleteDowntimeSchedule({ id: deleteData?.id });
|
||||
|
||||
const onDeleteHandler = (): void => {
|
||||
deleteDowntimeHandler({
|
||||
deleteDowntimeScheduleAsync,
|
||||
notifications,
|
||||
showErrorModal,
|
||||
refetchAllSchedules,
|
||||
deleteId: deleteData?.id,
|
||||
hideDeleteDowntimeScheduleModal,
|
||||
|
||||
@@ -14,18 +14,11 @@ import {
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
createDowntimeSchedule,
|
||||
updateDowntimeScheduleByID,
|
||||
} from 'api/generated/services/downtimeschedules';
|
||||
import type {
|
||||
RuletypesPlannedMaintenanceDTO,
|
||||
RuletypesPostablePlannedMaintenanceDTO,
|
||||
RuletypesRecurrenceDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { AxiosError } from 'axios';
|
||||
DowntimeSchedules,
|
||||
Recurrence,
|
||||
} from 'api/plannedDowntime/getAllDowntimeSchedules';
|
||||
import { DowntimeScheduleUpdatePayload } from 'api/plannedDowntime/updateDowntimeSchedule';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import {
|
||||
ModalButtonWrapper,
|
||||
@@ -36,14 +29,15 @@ import timezone from 'dayjs/plugin/timezone';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { defaultTo, isEmpty } from 'lodash-es';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
import { ALL_TIME_ZONES } from 'utils/timeZoneUtil';
|
||||
|
||||
import 'dayjs/locale/en';
|
||||
|
||||
import { SOMETHING_WENT_WRONG } from '../../constants/api';
|
||||
import { showErrorNotification } from '../../utils/error';
|
||||
import { AlertRuleTags } from './PlannedDowntimeList';
|
||||
import {
|
||||
createEditDowntimeSchedule,
|
||||
getAlertOptionsFromIds,
|
||||
getDurationInfo,
|
||||
getEndTime,
|
||||
@@ -68,9 +62,9 @@ interface PlannedDowntimeFormData {
|
||||
name: string;
|
||||
startTime: dayjs.Dayjs | string;
|
||||
endTime: dayjs.Dayjs | string;
|
||||
recurrence?: RuletypesRecurrenceDTO | null;
|
||||
recurrence?: Recurrence | null;
|
||||
alertRules: DefaultOptionType[];
|
||||
recurrenceSelect?: RuletypesRecurrenceDTO;
|
||||
recurrenceSelect?: Recurrence;
|
||||
timezone?: string;
|
||||
}
|
||||
|
||||
@@ -78,7 +72,7 @@ const customFormat = DATE_TIME_FORMATS.ORDINAL_DATETIME;
|
||||
|
||||
interface PlannedDowntimeFormProps {
|
||||
initialValues: Partial<
|
||||
RuletypesPlannedMaintenanceDTO & {
|
||||
DowntimeSchedules & {
|
||||
editMode: boolean;
|
||||
}
|
||||
>;
|
||||
@@ -117,9 +111,9 @@ export function PlannedDowntimeForm(
|
||||
?.unit || 'm',
|
||||
);
|
||||
|
||||
const [formData, setFormData] = useState<Partial<PlannedDowntimeFormData>>({
|
||||
timezone: initialValues.schedule?.timezone,
|
||||
});
|
||||
const [formData, setFormData] = useState<PlannedDowntimeFormData>(
|
||||
initialValues?.schedule as PlannedDowntimeFormData,
|
||||
);
|
||||
|
||||
const [recurrenceType, setRecurrenceType] = useState<string | null>(
|
||||
(initialValues.schedule?.recurrence?.repeatType as string) ||
|
||||
@@ -131,7 +125,6 @@ export function PlannedDowntimeForm(
|
||||
: undefined;
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const datePickerFooter = (mode: any): any =>
|
||||
mode === 'time' ? (
|
||||
@@ -141,54 +134,57 @@ export function PlannedDowntimeForm(
|
||||
const saveHanlder = useCallback(
|
||||
async (values: PlannedDowntimeFormData) => {
|
||||
const shouldKeepLocalTime = !isEditMode;
|
||||
const data: RuletypesPostablePlannedMaintenanceDTO = {
|
||||
alertIds: values.alertRules
|
||||
.map((alert) => alert.value)
|
||||
.filter((alert) => alert !== undefined) as string[],
|
||||
name: values.name,
|
||||
schedule: {
|
||||
startTime: new Date(
|
||||
handleTimeConversion(
|
||||
const createEditProps: DowntimeScheduleUpdatePayload = {
|
||||
data: {
|
||||
alertIds: values.alertRules
|
||||
.map((alert) => alert.value)
|
||||
.filter((alert) => alert !== undefined) as string[],
|
||||
name: values.name,
|
||||
schedule: {
|
||||
startTime: handleTimeConversion(
|
||||
values.startTime,
|
||||
timezoneInitialValue,
|
||||
values.timezone,
|
||||
shouldKeepLocalTime,
|
||||
),
|
||||
),
|
||||
timezone: values.timezone as string,
|
||||
endTime: values.endTime
|
||||
? new Date(
|
||||
handleTimeConversion(
|
||||
timezone: values.timezone,
|
||||
endTime: values.endTime
|
||||
? handleTimeConversion(
|
||||
values.endTime,
|
||||
timezoneInitialValue,
|
||||
values.timezone,
|
||||
shouldKeepLocalTime,
|
||||
),
|
||||
)
|
||||
: undefined,
|
||||
recurrence: values.recurrence as RuletypesRecurrenceDTO,
|
||||
)
|
||||
: undefined,
|
||||
recurrence: values.recurrence as Recurrence,
|
||||
},
|
||||
},
|
||||
id: isEditMode ? initialValues.id : undefined,
|
||||
};
|
||||
|
||||
setSaveLoading(true);
|
||||
try {
|
||||
if (isEditMode && initialValues.id) {
|
||||
await updateDowntimeScheduleByID({ id: initialValues.id }, data);
|
||||
const response = await createEditDowntimeSchedule({ ...createEditProps });
|
||||
if (response.message === 'success') {
|
||||
setIsOpen(false);
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: isEditMode
|
||||
? 'Schedule updated successfully'
|
||||
: 'Schedule created successfully',
|
||||
});
|
||||
refetchAllSchedules();
|
||||
} else {
|
||||
await createDowntimeSchedule(data);
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description:
|
||||
typeof response.error === 'string'
|
||||
? response.error
|
||||
: response.error?.message || SOMETHING_WENT_WRONG,
|
||||
});
|
||||
}
|
||||
setIsOpen(false);
|
||||
notifications.success({
|
||||
message: 'Success',
|
||||
description: isEditMode
|
||||
? 'Schedule updated successfully'
|
||||
: 'Schedule created successfully',
|
||||
});
|
||||
refetchAllSchedules();
|
||||
} catch (e: unknown) {
|
||||
showErrorModal(
|
||||
convertToApiError(e as AxiosError<RenderErrorResponseDTO>) as APIError,
|
||||
);
|
||||
showErrorNotification(notifications, e as Error);
|
||||
}
|
||||
setSaveLoading(false);
|
||||
},
|
||||
@@ -199,11 +195,10 @@ export function PlannedDowntimeForm(
|
||||
refetchAllSchedules,
|
||||
setIsOpen,
|
||||
timezoneInitialValue,
|
||||
showErrorModal,
|
||||
],
|
||||
);
|
||||
const onFinish = async (values: PlannedDowntimeFormData): Promise<void> => {
|
||||
const recurrenceData =
|
||||
const recurrenceData: Recurrence | undefined =
|
||||
values?.recurrence?.repeatType === recurrenceOptions.doesNotRepeat.value
|
||||
? undefined
|
||||
: {
|
||||
@@ -230,10 +225,7 @@ export function PlannedDowntimeForm(
|
||||
repeatType: values.recurrence?.repeatType,
|
||||
};
|
||||
|
||||
const payloadValues = {
|
||||
...values,
|
||||
recurrence: recurrenceData as RuletypesRecurrenceDTO | undefined,
|
||||
};
|
||||
const payloadValues = { ...values, recurrence: recurrenceData };
|
||||
await saveHanlder(payloadValues);
|
||||
};
|
||||
|
||||
@@ -244,9 +236,11 @@ export function PlannedDowntimeForm(
|
||||
];
|
||||
|
||||
const handleOk = async (): Promise<void> => {
|
||||
await form.validateFields().catch(() => {
|
||||
// antd renders inline field-level errors; nothing more to do here.
|
||||
});
|
||||
try {
|
||||
await form.validateFields();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = (): void => {
|
||||
@@ -287,19 +281,18 @@ export function PlannedDowntimeForm(
|
||||
: '',
|
||||
recurrence: {
|
||||
...initialValues.schedule?.recurrence,
|
||||
repeatType: (!isScheduleRecurring(initialValues?.schedule)
|
||||
repeatType: !isScheduleRecurring(initialValues?.schedule)
|
||||
? recurrenceOptions.doesNotRepeat.value
|
||||
: initialValues.schedule?.recurrence
|
||||
?.repeatType) as RuletypesRecurrenceDTO['repeatType'],
|
||||
duration: String(
|
||||
getDurationInfo(initialValues.schedule?.recurrence?.duration as string)
|
||||
?.value ?? '',
|
||||
),
|
||||
} as RuletypesRecurrenceDTO,
|
||||
: (initialValues.schedule?.recurrence?.repeatType as string),
|
||||
duration: getDurationInfo(
|
||||
initialValues.schedule?.recurrence?.duration as string,
|
||||
)?.value,
|
||||
},
|
||||
timezone: initialValues.schedule?.timezone as string,
|
||||
};
|
||||
return formData;
|
||||
}, [initialValues, alertOptions]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initialValues]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedTags(formatedInitialValues.alertRules);
|
||||
@@ -332,12 +325,7 @@ export function PlannedDowntimeForm(
|
||||
const startTimeText = useMemo((): string => {
|
||||
let startTime = formData?.startTime;
|
||||
if (recurrenceType !== recurrenceOptions.doesNotRepeat.value) {
|
||||
startTime =
|
||||
(formData?.recurrence?.startTime
|
||||
? dayjs(formData.recurrence.startTime).toISOString()
|
||||
: '') ||
|
||||
formData?.startTime ||
|
||||
'';
|
||||
startTime = formData?.recurrence?.startTime || formData?.startTime || '';
|
||||
}
|
||||
|
||||
if (!startTime) {
|
||||
@@ -393,10 +381,7 @@ export function PlannedDowntimeForm(
|
||||
const endTimeText = useMemo((): string => {
|
||||
let endTime = formData?.endTime;
|
||||
if (recurrenceType !== recurrenceOptions.doesNotRepeat.value) {
|
||||
endTime =
|
||||
(formData?.recurrence?.endTime
|
||||
? dayjs(formData.recurrence.endTime).toISOString()
|
||||
: '') || '';
|
||||
endTime = formData?.recurrence?.endTime || '';
|
||||
|
||||
if (!isEditMode && !endTime) {
|
||||
endTime = formData?.endTime || '';
|
||||
|
||||
@@ -12,15 +12,13 @@ import {
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import type { DefaultOptionType } from 'antd/es/select';
|
||||
import type {
|
||||
ListDowntimeSchedules200,
|
||||
RenderErrorResponseDTO,
|
||||
RuletypesPlannedMaintenanceDTO,
|
||||
RuletypesRecurrenceDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import type { ErrorType } from 'api/generatedAPIInstance';
|
||||
import {
|
||||
DowntimeSchedules,
|
||||
PayloadProps,
|
||||
Recurrence,
|
||||
} from 'api/plannedDowntime/getAllDowntimeSchedules';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import cx from 'classnames';
|
||||
import dayjs from 'dayjs';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { defaultTo } from 'lodash-es';
|
||||
import { CalendarClock, PenLine, Trash2 } from 'lucide-react';
|
||||
@@ -145,7 +143,7 @@ export function CollapseListContent({
|
||||
created_by_name?: string;
|
||||
created_by_email?: string;
|
||||
timeframe: [string | undefined | null, string | undefined | null];
|
||||
repeats?: RuletypesRecurrenceDTO | null;
|
||||
repeats?: Recurrence | null;
|
||||
updated_at?: string;
|
||||
updated_by_name?: string;
|
||||
alertOptions?: DefaultOptionType[];
|
||||
@@ -220,10 +218,10 @@ export function CollapseListContent({
|
||||
export function CustomCollapseList(
|
||||
props: DowntimeSchedulesTableData & {
|
||||
setInitialValues: React.Dispatch<
|
||||
React.SetStateAction<Partial<RuletypesPlannedMaintenanceDTO>>
|
||||
React.SetStateAction<Partial<DowntimeSchedules>>
|
||||
>;
|
||||
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
handleDeleteDowntime: (id: string, name: string) => void;
|
||||
handleDeleteDowntime: (id: number, name: string) => void;
|
||||
setEditMode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
},
|
||||
): JSX.Element {
|
||||
@@ -243,19 +241,12 @@ export function CustomCollapseList(
|
||||
kind,
|
||||
} = props;
|
||||
|
||||
const scheduleTime = schedule?.startTime
|
||||
? dayjs(schedule.startTime).toISOString()
|
||||
: createdAt
|
||||
? dayjs(createdAt).toISOString()
|
||||
: '';
|
||||
const scheduleTime = schedule?.startTime ? schedule.startTime : createdAt;
|
||||
// Combine time and date
|
||||
const formattedDateAndTime = `Start time ⎯ ${formatDateTime(
|
||||
defaultTo(scheduleTime, ''),
|
||||
)} ${schedule?.timezone}`;
|
||||
const endTime = getEndTime({
|
||||
kind,
|
||||
schedule,
|
||||
} as Partial<RuletypesPlannedMaintenanceDTO>);
|
||||
const endTime = getEndTime({ kind, schedule });
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -266,10 +257,7 @@ export function CustomCollapseList(
|
||||
duration={
|
||||
schedule?.recurrence?.duration
|
||||
? (schedule?.recurrence?.duration as string)
|
||||
: getDuration(
|
||||
schedule?.startTime ? dayjs(schedule.startTime).toISOString() : '',
|
||||
schedule?.endTime ? dayjs(schedule.endTime).toISOString() : '',
|
||||
)
|
||||
: getDuration(schedule?.startTime, schedule?.endTime)
|
||||
}
|
||||
name={defaultTo(name, '')}
|
||||
handleEdit={(): void => {
|
||||
@@ -278,23 +266,21 @@ export function CustomCollapseList(
|
||||
setEditMode(true);
|
||||
}}
|
||||
handleDelete={(): void => {
|
||||
handleDeleteDowntime(id ?? '', name || '');
|
||||
handleDeleteDowntime(id, name || '');
|
||||
}}
|
||||
/>
|
||||
}
|
||||
key={id ?? ''}
|
||||
key={id}
|
||||
>
|
||||
<CollapseListContent
|
||||
created_at={createdAt ? dayjs(createdAt).toISOString() : ''}
|
||||
created_at={defaultTo(createdAt, '')}
|
||||
created_by_name={defaultTo(createdBy, '')}
|
||||
timeframe={[
|
||||
schedule?.startTime?.toString(),
|
||||
typeof endTime === 'string' ? endTime : endTime?.toString(),
|
||||
]}
|
||||
repeats={
|
||||
schedule?.recurrence as RuletypesRecurrenceDTO | null | undefined
|
||||
}
|
||||
updated_at={updatedAt ? dayjs(updatedAt).toISOString() : ''}
|
||||
repeats={schedule?.recurrence}
|
||||
updated_at={defaultTo(updatedAt, '')}
|
||||
updated_by_name={defaultTo(updatedBy, '')}
|
||||
alertOptions={alertOptions}
|
||||
timezone={defaultTo(schedule?.timezone, '')}
|
||||
@@ -309,7 +295,7 @@ export function CustomCollapseList(
|
||||
);
|
||||
}
|
||||
|
||||
export type DowntimeSchedulesTableData = RuletypesPlannedMaintenanceDTO & {
|
||||
export type DowntimeSchedulesTableData = DowntimeSchedules & {
|
||||
alertOptions: DefaultOptionType[];
|
||||
};
|
||||
|
||||
@@ -323,15 +309,15 @@ export function PlannedDowntimeList({
|
||||
searchValue,
|
||||
}: {
|
||||
downtimeSchedules: UseQueryResult<
|
||||
ListDowntimeSchedules200,
|
||||
ErrorType<RenderErrorResponseDTO>
|
||||
AxiosResponse<PayloadProps, any>,
|
||||
AxiosError<unknown, any>
|
||||
>;
|
||||
alertOptions: DefaultOptionType[];
|
||||
setInitialValues: React.Dispatch<
|
||||
React.SetStateAction<Partial<RuletypesPlannedMaintenanceDTO>>
|
||||
React.SetStateAction<Partial<DowntimeSchedules>>
|
||||
>;
|
||||
setModalOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
handleDeleteDowntime: (id: string, name: string) => void;
|
||||
handleDeleteDowntime: (id: number, name: string) => void;
|
||||
setEditMode: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
searchValue: string | number;
|
||||
}): JSX.Element {
|
||||
@@ -351,19 +337,19 @@ export function PlannedDowntimeList({
|
||||
];
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const tableData = [...(downtimeSchedules.data?.data || [])]
|
||||
const tableData = (downtimeSchedules.data?.data?.data || [])
|
||||
.sort((a, b): number => {
|
||||
if (a?.updatedAt && b?.updatedAt) {
|
||||
return dayjs(b.updatedAt).diff(dayjs(a.updatedAt));
|
||||
return b.updatedAt.localeCompare(a.updatedAt);
|
||||
}
|
||||
return 0;
|
||||
})
|
||||
.filter(
|
||||
?.filter(
|
||||
(data) =>
|
||||
data.name.includes(searchValue.toLocaleString()) ||
|
||||
data.id === searchValue.toLocaleString(),
|
||||
data?.name?.includes(searchValue.toLocaleString()) ||
|
||||
data?.id.toLocaleString() === searchValue.toLocaleString(),
|
||||
)
|
||||
.map((data) => {
|
||||
.map?.((data) => {
|
||||
const specificAlertOptions = getAlertOptionsFromIds(
|
||||
data.alertIds || [],
|
||||
alertOptions,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user