mirror of
https://github.com/SigNoz/signoz.git
synced 2026-05-22 18:00:25 +01:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77e7798779 | ||
|
|
7274421895 | ||
|
|
9c6656d6b9 | ||
|
|
5c54a2537c | ||
|
|
bf201710a7 | ||
|
|
a5adc52276 | ||
|
|
5ddcf33811 | ||
|
|
c0fe996e7a |
@@ -2689,7 +2689,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesClusterRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
@@ -2759,7 +2758,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesDaemonSetRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
@@ -2829,7 +2827,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesDeploymentRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
@@ -2908,7 +2905,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesHostRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
@@ -2984,7 +2980,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesJobRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
@@ -3032,7 +3027,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNamespaceRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
@@ -3110,7 +3104,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesNodeRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
@@ -3209,7 +3202,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesPodRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
@@ -3554,7 +3546,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesStatefulSetRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
@@ -3615,7 +3606,6 @@ components:
|
||||
records:
|
||||
items:
|
||||
$ref: '#/components/schemas/InframonitoringtypesVolumeRecord'
|
||||
nullable: true
|
||||
type: array
|
||||
requiredMetricsCheck:
|
||||
$ref: '#/components/schemas/InframonitoringtypesRequiredMetricsCheck'
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
"@signozhq/design-tokens": "2.1.4",
|
||||
"@signozhq/icons": "0.4.0",
|
||||
"@signozhq/resizable": "0.0.2",
|
||||
"@signozhq/ui": "0.0.19",
|
||||
"@signozhq/ui": "0.0.21",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "3.13.22",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
|
||||
31
frontend/pnpm-lock.yaml
generated
31
frontend/pnpm-lock.yaml
generated
@@ -77,8 +77,8 @@ importers:
|
||||
specifier: 0.0.2
|
||||
version: 0.0.2(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@signozhq/ui':
|
||||
specifier: 0.0.19
|
||||
version: 0.0.19(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)
|
||||
specifier: 0.0.21
|
||||
version: 0.0.21(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)
|
||||
'@tanstack/react-table':
|
||||
specifier: 8.21.3
|
||||
version: 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
@@ -3269,8 +3269,8 @@ packages:
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
|
||||
'@signozhq/ui@0.0.19':
|
||||
resolution: {integrity: sha512-2q6aRxN/PR4PlR2xJZAREEuvLPiDFggfFKzCW2Z5vHVVbrgnvZHWD1jPUuwszfEg0ceH3UvkwqceO7wN4uRJAA==}
|
||||
'@signozhq/ui@0.0.21':
|
||||
resolution: {integrity: sha512-uLM3Vqwxlk2USXbwtb3qRLpjZR9b9QSHFQq/jtcfYNMDmIE/sNjSj0nRkEhX4RqqRgsLRt2PVA33aeWxDOLO3g==}
|
||||
peerDependencies:
|
||||
'@signozhq/icons': 0.3.0
|
||||
react: ^18.2.0
|
||||
@@ -3851,27 +3851,6 @@ packages:
|
||||
peerDependencies:
|
||||
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
|
||||
|
||||
'@webassemblyjs/ast@1.14.1':
|
||||
resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==}
|
||||
|
||||
'@webassemblyjs/floating-point-hex-parser@1.13.2':
|
||||
resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==}
|
||||
|
||||
'@webassemblyjs/helper-api-error@1.13.2':
|
||||
resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==}
|
||||
|
||||
'@webassemblyjs/helper-buffer@1.14.1':
|
||||
resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==}
|
||||
|
||||
'@webassemblyjs/helper-numbers@1.13.2':
|
||||
resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==}
|
||||
|
||||
'@webassemblyjs/helper-wasm-bytecode@1.13.2':
|
||||
resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==}
|
||||
|
||||
'@webassemblyjs/helper-wasm-section@1.14.1':
|
||||
resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==}
|
||||
|
||||
'@xmldom/xmldom@0.8.13':
|
||||
resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
@@ -12034,7 +12013,7 @@ snapshots:
|
||||
- react-dom
|
||||
- tailwindcss
|
||||
|
||||
'@signozhq/ui@0.0.19(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)':
|
||||
'@signozhq/ui@0.0.21(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.3(react@18.2.0))(react@18.2.0)':
|
||||
dependencies:
|
||||
'@chenglou/pretext': 0.0.5
|
||||
'@radix-ui/react-checkbox': 1.3.3(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
|
||||
@@ -166,6 +166,7 @@ function createMockAppContext(
|
||||
userPreferences: [],
|
||||
hostsData: null,
|
||||
isLoggedIn: true,
|
||||
isPreflightLoading: false,
|
||||
org: [{ createdAt: 0, id: 'org-id', displayName: 'Test Org' }],
|
||||
isFetchingUser: false,
|
||||
isFetchingActiveLicense: false,
|
||||
|
||||
@@ -59,6 +59,7 @@ function App(): JSX.Element {
|
||||
isLoggedIn: isLoggedInState,
|
||||
featureFlags,
|
||||
org,
|
||||
isPreflightLoading,
|
||||
} = useAppContext();
|
||||
const [routes, setRoutes] = useState<AppRoutes[]>(defaultRoutes);
|
||||
const isAIAssistantEnabled = useIsAIAssistantEnabled();
|
||||
@@ -386,6 +387,10 @@ function App(): JSX.Element {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isCloudUser, isEnterpriseSelfHostedUser]);
|
||||
|
||||
if (isPreflightLoading) {
|
||||
return <Spinner tip="Loading..." />;
|
||||
}
|
||||
|
||||
// if the user is in logged in state
|
||||
if (isLoggedInState) {
|
||||
// if the setup calls are loading then return a spinner
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import axios from 'axios';
|
||||
import { getIsNoAuthMode } from 'utils/noAuthMode';
|
||||
|
||||
import { interceptorRejected } from '../index';
|
||||
|
||||
jest.mock('utils/noAuthMode', () => ({
|
||||
getIsNoAuthMode: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('api/v2/sessions/rotate/post', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('AppRoutes/utils', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../utils', () => ({
|
||||
Logout: jest.fn(),
|
||||
}));
|
||||
|
||||
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
|
||||
const post = require('api/v2/sessions/rotate/post').default;
|
||||
// oxlint-disable-next-line typescript/no-require-imports typescript/no-var-requires
|
||||
const { Logout } = require('../utils');
|
||||
|
||||
describe('interceptorRejected — no-auth mode', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.spyOn(axios, 'isAxiosError').mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('does NOT call rotate or Logout when no-auth mode is enabled on 401', async () => {
|
||||
(getIsNoAuthMode as jest.Mock).mockReturnValue(true);
|
||||
|
||||
const error = {
|
||||
isAxiosError: true,
|
||||
response: {
|
||||
status: 401,
|
||||
config: { url: '/dashboards', method: 'get' },
|
||||
},
|
||||
config: { url: '/dashboards', headers: {} },
|
||||
};
|
||||
|
||||
await interceptorRejected(error as any).catch(() => {});
|
||||
|
||||
expect(post).not.toHaveBeenCalled();
|
||||
expect(Logout).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('DOES attempt rotate when no-auth mode is disabled on 401', async () => {
|
||||
(getIsNoAuthMode as jest.Mock).mockReturnValue(false);
|
||||
(post as jest.Mock).mockResolvedValue({
|
||||
data: { accessToken: 'a', refreshToken: 'b' },
|
||||
});
|
||||
|
||||
const error = {
|
||||
isAxiosError: true,
|
||||
response: {
|
||||
status: 401,
|
||||
config: { url: '/dashboards', method: 'get' },
|
||||
},
|
||||
config: { url: '/dashboards', headers: {} },
|
||||
};
|
||||
|
||||
await interceptorRejected(error as any).catch(() => {});
|
||||
|
||||
expect(post).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -3488,9 +3488,9 @@ export interface InframonitoringtypesClustersDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesClusterRecordDTO[] | null;
|
||||
records: InframonitoringtypesClusterRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -3566,9 +3566,9 @@ export interface InframonitoringtypesDaemonSetsDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesDaemonSetRecordDTO[] | null;
|
||||
records: InframonitoringtypesDaemonSetRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -3644,9 +3644,9 @@ export interface InframonitoringtypesDeploymentsDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesDeploymentRecordDTO[] | null;
|
||||
records: InframonitoringtypesDeploymentRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -3730,9 +3730,9 @@ export interface InframonitoringtypesHostsDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesHostRecordDTO[] | null;
|
||||
records: InframonitoringtypesHostRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -3816,9 +3816,9 @@ export interface InframonitoringtypesJobsDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesJobRecordDTO[] | null;
|
||||
records: InframonitoringtypesJobRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -3866,9 +3866,9 @@ export interface InframonitoringtypesNamespacesDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesNamespaceRecordDTO[] | null;
|
||||
records: InframonitoringtypesNamespaceRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -3933,9 +3933,9 @@ export interface InframonitoringtypesNodesDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesNodeRecordDTO[] | null;
|
||||
records: InframonitoringtypesNodeRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -4017,9 +4017,9 @@ export interface InframonitoringtypesPodsDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesPodRecordDTO[] | null;
|
||||
records: InframonitoringtypesPodRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -4437,9 +4437,9 @@ export interface InframonitoringtypesStatefulSetsDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesStatefulSetRecordDTO[] | null;
|
||||
records: InframonitoringtypesStatefulSetRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
@@ -4506,9 +4506,9 @@ export interface InframonitoringtypesVolumesDTO {
|
||||
*/
|
||||
endTimeBeforeRetention: boolean;
|
||||
/**
|
||||
* @type array,null
|
||||
* @type array
|
||||
*/
|
||||
records: InframonitoringtypesVolumeRecordDTO[] | null;
|
||||
records: InframonitoringtypesVolumeRecordDTO[];
|
||||
requiredMetricsCheck: InframonitoringtypesRequiredMetricsCheckDTO;
|
||||
/**
|
||||
* @type integer
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Events } from 'constants/events';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { getBasePath } from 'utils/basePath';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
import { getIsNoAuthMode } from 'utils/noAuthMode';
|
||||
|
||||
import apiV1, { apiAlertManager, apiV2, apiV3, apiV4, apiV5 } from './apiV1';
|
||||
import { Logout } from './utils';
|
||||
@@ -108,7 +109,10 @@ export const interceptorRejected = async (
|
||||
if (axios.isAxiosError(value) && value.response) {
|
||||
const { response } = value;
|
||||
|
||||
const isNoAuthMode = getIsNoAuthMode();
|
||||
|
||||
if (
|
||||
!isNoAuthMode &&
|
||||
response.status === 401 &&
|
||||
// if the session rotate call or the create session errors out with 401 or the delete sessions call returns 401 then we do not retry!
|
||||
response.config.url !== '/sessions/rotate' &&
|
||||
@@ -140,16 +144,20 @@ export const interceptorRejected = async (
|
||||
return await Promise.resolve(reResponse);
|
||||
} catch (error) {
|
||||
if ((error as AxiosError)?.response?.status === 401) {
|
||||
Logout();
|
||||
void Logout();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
Logout();
|
||||
void Logout();
|
||||
}
|
||||
}
|
||||
|
||||
if (response.status === 401 && response.config.url === '/sessions/rotate') {
|
||||
Logout();
|
||||
if (
|
||||
!isNoAuthMode &&
|
||||
response.status === 401 &&
|
||||
response.config.url === '/sessions/rotate'
|
||||
) {
|
||||
void Logout();
|
||||
}
|
||||
}
|
||||
return await Promise.reject(value);
|
||||
|
||||
@@ -137,7 +137,6 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
<AuthZTooltip checks={[SACreatePermission]}>
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form="create-sa-form"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
|
||||
@@ -11,9 +11,6 @@ import { GuardAuthZ } from './GuardAuthZ';
|
||||
describe('GuardAuthZ', () => {
|
||||
const TestChild = (): ReactElement => <div>Protected Content</div>;
|
||||
const LoadingFallback = (): ReactElement => <div>Loading...</div>;
|
||||
const ErrorFallback = (error: Error): ReactElement => (
|
||||
<div>Error occurred: {error.message}</div>
|
||||
);
|
||||
const NoPermissionFallback = (_response: {
|
||||
requiredPermissionName: BrandedPermission;
|
||||
}): ReactElement => <div>Access denied</div>;
|
||||
@@ -90,40 +87,28 @@ describe('GuardAuthZ', () => {
|
||||
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render fallbackOnError when API error occurs', async () => {
|
||||
const errorMessage = 'Internal Server Error';
|
||||
|
||||
it('should render children when API error occurs and no fallbackOnError provided (fail open)', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
|
||||
return res(ctx.status(500), ctx.json({ error: errorMessage }));
|
||||
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<GuardAuthZ relation="read" object="role:*" fallbackOnError={ErrorFallback}>
|
||||
<GuardAuthZ relation="read" object="role:*">
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Error occurred:/)).toBeInTheDocument();
|
||||
expect(screen.getByText('Protected Content')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should pass error object to fallbackOnError function', async () => {
|
||||
const errorMessage = 'Network request failed';
|
||||
let receivedError: Error | null = null;
|
||||
|
||||
const errorFallbackWithCapture = (error: Error): ReactElement => {
|
||||
receivedError = error;
|
||||
return <div>Captured error: {error.message}</div>;
|
||||
};
|
||||
|
||||
it('should render fallbackOnError when API error occurs and fallbackOnError is provided', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
|
||||
return res(ctx.status(500), ctx.json({ error: errorMessage }));
|
||||
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -131,35 +116,14 @@ describe('GuardAuthZ', () => {
|
||||
<GuardAuthZ
|
||||
relation="read"
|
||||
object="role:*"
|
||||
fallbackOnError={errorFallbackWithCapture}
|
||||
fallbackOnError={<div>Custom error fallback</div>}
|
||||
>
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(receivedError).not.toBeNull();
|
||||
});
|
||||
|
||||
expect(receivedError).toBeInstanceOf(Error);
|
||||
expect(screen.getByText(/Captured error:/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render null when error occurs and no fallbackOnError provided', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
|
||||
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
|
||||
}),
|
||||
);
|
||||
|
||||
const { container } = render(
|
||||
<GuardAuthZ relation="read" object="role:*">
|
||||
<TestChild />
|
||||
</GuardAuthZ>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(container.firstChild).toBeNull();
|
||||
expect(screen.getByText('Custom error fallback')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
|
||||
@@ -12,7 +12,7 @@ export type GuardAuthZProps<R extends AuthZRelation> = {
|
||||
relation: R;
|
||||
object: AuthZObject<R>;
|
||||
fallbackOnLoading?: JSX.Element;
|
||||
fallbackOnError?: (error: Error) => JSX.Element;
|
||||
fallbackOnError?: JSX.Element;
|
||||
fallbackOnNoPermissions?: (response: {
|
||||
requiredPermissionName: BrandedPermission;
|
||||
}) => JSX.Element;
|
||||
@@ -35,7 +35,7 @@ export function GuardAuthZ<R extends AuthZRelation>({
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return fallbackOnError?.(error) ?? null;
|
||||
return fallbackOnError ?? children;
|
||||
}
|
||||
|
||||
if (!permissions?.[permission]?.isGranted) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
.banner {
|
||||
height: var(--spacing-20);
|
||||
|
||||
a {
|
||||
color: var(--callout-warning-title);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: var(--callout-warning-title);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
26
frontend/src/components/NoAuthBanner/NoAuthBanner.tsx
Normal file
26
frontend/src/components/NoAuthBanner/NoAuthBanner.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { PersistedAnnouncementBanner } from '@signozhq/ui/announcement-banner';
|
||||
|
||||
import styles from './NoAuthBanner.module.scss';
|
||||
|
||||
export function NoAuthBanner(): JSX.Element {
|
||||
return (
|
||||
<PersistedAnnouncementBanner
|
||||
type="warning"
|
||||
storageKey="no-auth-banner-v1"
|
||||
testId="no-auth-banner"
|
||||
className={styles.banner}
|
||||
>
|
||||
Impersonation mode: authentication is disabled. Anyone with access to this
|
||||
instance has admin privileges.{' '}
|
||||
<a
|
||||
href="https://signoz.io/docs/manage/administrator-guide/configuration/impersonation-mode/"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more
|
||||
</a>
|
||||
</PersistedAnnouncementBanner>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoAuthBanner;
|
||||
@@ -0,0 +1,24 @@
|
||||
import { render, screen } from 'tests/test-utils';
|
||||
|
||||
import { NoAuthBanner } from '../NoAuthBanner';
|
||||
|
||||
describe('NoAuthBanner', () => {
|
||||
it('renders the no-auth message', () => {
|
||||
render(<NoAuthBanner />);
|
||||
expect(
|
||||
screen.getByText(/Impersonation mode: authentication is disabled/i),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders with the warning test id', () => {
|
||||
render(<NoAuthBanner />);
|
||||
expect(screen.getByTestId('no-auth-banner')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a docs link that opens in a new tab', () => {
|
||||
render(<NoAuthBanner />);
|
||||
const link = screen.getByRole('link', { name: /learn more/i });
|
||||
expect(link).toHaveAttribute('target', '_blank');
|
||||
expect(link).toHaveAttribute('rel', 'noreferrer');
|
||||
});
|
||||
});
|
||||
@@ -127,7 +127,6 @@ function KeyFormPhase({
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
|
||||
@@ -190,7 +190,6 @@ function EditKeyForm({
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { KeyRound, X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { Skeleton, Table } from 'antd';
|
||||
import { Skeleton, Table, Tooltip } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import AuthZTooltip from 'components/AuthZTooltip/AuthZTooltip';
|
||||
@@ -110,28 +110,34 @@ function buildColumns({
|
||||
onClick: (e): void => e.stopPropagation(),
|
||||
style: { cursor: 'default' },
|
||||
}),
|
||||
render: (_, record): JSX.Element => (
|
||||
<AuthZTooltip
|
||||
checks={[
|
||||
buildAPIKeyDeletePermission(record.id),
|
||||
buildSADetachPermission(accountId),
|
||||
]}
|
||||
enabled={!isDisabled && !!accountId}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
color="destructive"
|
||||
disabled={isDisabled}
|
||||
onClick={(): void => {
|
||||
onRevokeClick(record.id);
|
||||
}}
|
||||
className="keys-tab__revoke-btn"
|
||||
render: (_, record): JSX.Element => {
|
||||
const tooltipTitle = isDisabled ? 'Service account disabled' : 'Revoke Key';
|
||||
return (
|
||||
<AuthZTooltip
|
||||
checks={[
|
||||
buildAPIKeyDeletePermission(record.id),
|
||||
buildSADetachPermission(accountId),
|
||||
]}
|
||||
enabled={!isDisabled && !!accountId}
|
||||
>
|
||||
<X size={12} />
|
||||
</Button>
|
||||
</AuthZTooltip>
|
||||
),
|
||||
<Tooltip title={tooltipTitle}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
color="destructive"
|
||||
disabled={isDisabled}
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
onRevokeClick(record.id);
|
||||
}}
|
||||
className="keys-tab__revoke-btn"
|
||||
>
|
||||
<X size={12} />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</AuthZTooltip>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ describe('createGuardedRoute', () => {
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render error fallback when API error occurs', async () => {
|
||||
it('should render the component when API error occurs (fail open)', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
|
||||
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
|
||||
@@ -230,12 +230,8 @@ describe('createGuardedRoute', () => {
|
||||
render(<GuardedComponent {...props} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Component: test-value')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByText('Test Component: test-value'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render no permissions fallback when permission is denied', async () => {
|
||||
@@ -9,14 +9,11 @@ import { parsePermission } from 'hooks/useAuthZ/utils';
|
||||
|
||||
import noDataUrl from '@/assets/Icons/no-data.svg';
|
||||
|
||||
import ErrorBoundaryFallback from '../../pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import AppLoading from '../AppLoading/AppLoading';
|
||||
import { GuardAuthZ } from '../GuardAuthZ/GuardAuthZ';
|
||||
|
||||
import './createGuardedRoute.styles.scss';
|
||||
|
||||
const onErrorFallback = (): JSX.Element => <ErrorBoundaryFallback />;
|
||||
|
||||
function OnNoPermissionsFallback(response: {
|
||||
requiredPermissionName: BrandedPermission;
|
||||
}): ReactElement {
|
||||
@@ -63,7 +60,6 @@ export function createGuardedRoute<P extends object, R extends AuthZRelation>(
|
||||
relation={relation}
|
||||
object={resolvedObject}
|
||||
fallbackOnLoading={<AppLoading />}
|
||||
fallbackOnError={onErrorFallback}
|
||||
fallbackOnNoPermissions={(response): ReactElement => (
|
||||
<OnNoPermissionsFallback {...response} />
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
|
||||
import { usePanelContextMenu } from '../usePanelContextMenu';
|
||||
|
||||
// The hook composes `useCoordinates` (popover state) and `useGraphContextMenu`
|
||||
// (menu items). We mock both so the test focuses on the `enableDrillDown` gate
|
||||
// rather than the implementation of the menu wiring itself.
|
||||
const onClickMock = jest.fn();
|
||||
jest.mock('periscope/components/ContextMenu', () => ({
|
||||
useCoordinates: (): unknown => ({
|
||||
coordinates: null,
|
||||
popoverPosition: null,
|
||||
clickedData: null,
|
||||
onClose: jest.fn(),
|
||||
subMenu: null,
|
||||
onClick: onClickMock,
|
||||
setSubMenu: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/QueryTable/Drilldown/useGraphContextMenu', () => ({
|
||||
__esModule: true,
|
||||
default: (): { menuItemsConfig: { header: string; items: string } } => ({
|
||||
menuItemsConfig: { header: 'menu-header', items: 'menu-items' },
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('container/QueryTable/Drilldown/drilldownUtils', () => ({
|
||||
getUplotClickData: jest.fn(() => ({
|
||||
coord: { x: 1, y: 2 },
|
||||
record: { queryName: 'A', filters: [] },
|
||||
label: 'lbl',
|
||||
seriesColor: '#abc',
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('container/PanelWrapper/utils', () => ({
|
||||
isApmMetric: jest.fn(() => false),
|
||||
getTimeRangeFromStepInterval: jest.fn(() => ({ start: 0, end: 0 })),
|
||||
}));
|
||||
|
||||
const mockWidget = { id: 'w-1', query: {} } as unknown as Widgets;
|
||||
const mockQueryResponse = {
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
} as unknown as UseQueryResult<
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
|
||||
describe('usePanelContextMenu', () => {
|
||||
beforeEach(() => {
|
||||
onClickMock.mockClear();
|
||||
});
|
||||
|
||||
it('returns empty menuItemsConfig when enableDrillDown is false', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelContextMenu({
|
||||
widget: mockWidget,
|
||||
queryResponse: mockQueryResponse,
|
||||
enableDrillDown: false,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.menuItemsConfig).toStrictEqual({});
|
||||
});
|
||||
|
||||
it('returns wired menuItemsConfig when enableDrillDown is true', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelContextMenu({
|
||||
widget: mockWidget,
|
||||
queryResponse: mockQueryResponse,
|
||||
enableDrillDown: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.menuItemsConfig).toStrictEqual({
|
||||
header: 'menu-header',
|
||||
items: 'menu-items',
|
||||
});
|
||||
});
|
||||
|
||||
it('clickHandlerWithContextMenu is a no-op when enableDrillDown is false', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelContextMenu({
|
||||
widget: mockWidget,
|
||||
queryResponse: mockQueryResponse,
|
||||
enableDrillDown: false,
|
||||
}),
|
||||
);
|
||||
|
||||
result.current.clickHandlerWithContextMenu(
|
||||
100, // xValue
|
||||
200, // yValue
|
||||
0, // mouseX
|
||||
0, // mouseY
|
||||
{ serviceName: 'svc' }, // metric
|
||||
{ queryName: 'A', inFocusOrNot: true }, // queryData
|
||||
10, // absoluteMouseX
|
||||
20, // absoluteMouseY
|
||||
{}, // axesData
|
||||
{ seriesIndex: 0, seriesName: 'A', value: 1, color: '#abc' }, // focusedSeries
|
||||
);
|
||||
|
||||
expect(onClickMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('clickHandlerWithContextMenu opens popover when enableDrillDown is true', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelContextMenu({
|
||||
widget: mockWidget,
|
||||
queryResponse: mockQueryResponse,
|
||||
enableDrillDown: true,
|
||||
}),
|
||||
);
|
||||
|
||||
result.current.clickHandlerWithContextMenu(
|
||||
100,
|
||||
200,
|
||||
0,
|
||||
0,
|
||||
{ serviceName: 'svc' },
|
||||
{ queryName: 'A', inFocusOrNot: true },
|
||||
10,
|
||||
20,
|
||||
{},
|
||||
{ seriesIndex: 0, seriesName: 'A', value: 1, color: '#abc' },
|
||||
);
|
||||
|
||||
expect(onClickMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('defaults to disabled when enableDrillDown is not provided', () => {
|
||||
const { result } = renderHook(() =>
|
||||
usePanelContextMenu({
|
||||
widget: mockWidget,
|
||||
queryResponse: mockQueryResponse,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result.current.menuItemsConfig).toStrictEqual({});
|
||||
});
|
||||
});
|
||||
@@ -21,11 +21,13 @@ interface UseTimeSeriesContextMenuParams {
|
||||
SuccessResponse<MetricRangePayloadProps, unknown>,
|
||||
Error
|
||||
>;
|
||||
enableDrillDown?: boolean;
|
||||
}
|
||||
|
||||
export const usePanelContextMenu = ({
|
||||
widget,
|
||||
queryResponse,
|
||||
enableDrillDown = false,
|
||||
}: UseTimeSeriesContextMenuParams): {
|
||||
coordinates: { x: number; y: number } | null;
|
||||
popoverPosition: PopoverPosition | null;
|
||||
@@ -61,6 +63,9 @@ export const usePanelContextMenu = ({
|
||||
|
||||
const clickHandlerWithContextMenu = useCallback(
|
||||
(...args: any[]) => {
|
||||
if (!enableDrillDown) {
|
||||
return;
|
||||
}
|
||||
const [
|
||||
xValue,
|
||||
_yvalue,
|
||||
@@ -112,14 +117,14 @@ export const usePanelContextMenu = ({
|
||||
});
|
||||
}
|
||||
},
|
||||
[onClick, queryResponse],
|
||||
[enableDrillDown, onClick, queryResponse],
|
||||
);
|
||||
|
||||
return {
|
||||
coordinates,
|
||||
popoverPosition,
|
||||
onClose,
|
||||
menuItemsConfig,
|
||||
menuItemsConfig: enableDrillDown ? menuItemsConfig : {},
|
||||
clickHandlerWithContextMenu,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
|
||||
isFullViewMode,
|
||||
onToggleModelHandler,
|
||||
groupByPerQuery,
|
||||
enableDrillDown = false,
|
||||
} = props;
|
||||
const uPlotRef = useRef<uPlot | null>(null);
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
@@ -61,6 +62,7 @@ function BarPanel(props: PanelWrapperProps): JSX.Element {
|
||||
} = usePanelContextMenu({
|
||||
widget,
|
||||
queryResponse,
|
||||
enableDrillDown,
|
||||
});
|
||||
|
||||
const config = useMemo(() => {
|
||||
|
||||
@@ -31,6 +31,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
|
||||
isFullViewMode,
|
||||
onToggleModelHandler,
|
||||
groupByPerQuery,
|
||||
enableDrillDown = false,
|
||||
} = props;
|
||||
const graphRef = useRef<HTMLDivElement>(null);
|
||||
const [minTimeScale, setMinTimeScale] = useState<number>();
|
||||
@@ -60,6 +61,7 @@ function TimeSeriesPanel(props: PanelWrapperProps): JSX.Element {
|
||||
} = usePanelContextMenu({
|
||||
widget,
|
||||
queryResponse,
|
||||
enableDrillDown,
|
||||
});
|
||||
|
||||
const chartData = useMemo(() => {
|
||||
|
||||
@@ -292,6 +292,8 @@ function FullView({
|
||||
return <Spinner height="100%" size="large" tip="Loading..." />;
|
||||
}
|
||||
|
||||
const showEditBtn = editWidget && dashboardEditView;
|
||||
|
||||
return (
|
||||
<div className="full-view-container">
|
||||
<OverlayScrollbar>
|
||||
@@ -306,7 +308,7 @@ function FullView({
|
||||
Reset Query
|
||||
</Button>
|
||||
)}
|
||||
{editWidget && (
|
||||
{showEditBtn && (
|
||||
<Button
|
||||
className="switch-edit-btn"
|
||||
disabled={response.isFetching || response.isLoading}
|
||||
|
||||
@@ -18,6 +18,8 @@ import listUserPreferences from 'api/v1/user/preferences/list';
|
||||
import updateUserPreferenceAPI from 'api/v1/user/preferences/name/update';
|
||||
import Header from 'components/Header/Header';
|
||||
import HeaderRightSection from 'components/HeaderRightSection/HeaderRightSection';
|
||||
import NoAuthBanner from 'components/NoAuthBanner/NoAuthBanner';
|
||||
import { getIsNoAuthMode } from 'utils/noAuthMode';
|
||||
import { ENTITY_VERSION_V5 } from 'constants/app';
|
||||
import { ORG_PREFERENCES } from 'constants/orgPreferences';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
@@ -196,7 +198,7 @@ export default function Home(): JSX.Element {
|
||||
const { mutate: updateUserPreference } = useMutation(updateUserPreferenceAPI, {
|
||||
onSuccess: () => {
|
||||
setUpdatingUserPreferences(false);
|
||||
refetchUserPreferences();
|
||||
void refetchUserPreferences();
|
||||
},
|
||||
onError: () => {
|
||||
setUpdatingUserPreferences(false);
|
||||
@@ -204,7 +206,7 @@ export default function Home(): JSX.Element {
|
||||
});
|
||||
|
||||
const handleWillDoThisLater = (): void => {
|
||||
logEvent('Welcome Checklist: Will do this later clicked', {});
|
||||
void logEvent('Welcome Checklist: Will do this later clicked', {});
|
||||
setUpdatingUserPreferences(true);
|
||||
|
||||
updateUserPreference({
|
||||
@@ -271,11 +273,12 @@ export default function Home(): JSX.Element {
|
||||
}, [metricsOnboardingData, handleUpdateChecklistDoneItem]);
|
||||
|
||||
useEffect(() => {
|
||||
logEvent('Homepage: Visited', {});
|
||||
void logEvent('Homepage: Visited', {});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="home-container">
|
||||
{getIsNoAuthMode() && <NoAuthBanner />}
|
||||
<div className="sticky-header">
|
||||
<Header
|
||||
leftComponent={
|
||||
@@ -298,9 +301,9 @@ export default function Home(): JSX.Element {
|
||||
autoAdjustOverflow
|
||||
onOpenChange={(visible): void => {
|
||||
if (visible) {
|
||||
logEvent('Welcome Checklist: Expanded', {});
|
||||
void logEvent('Welcome Checklist: Expanded', {});
|
||||
} else {
|
||||
logEvent('Welcome Checklist: Minimized', {});
|
||||
void logEvent('Welcome Checklist: Minimized', {});
|
||||
}
|
||||
}}
|
||||
content={renderWelcomeChecklistModal()}
|
||||
@@ -353,7 +356,7 @@ export default function Home(): JSX.Element {
|
||||
className="active-ingestion-card-actions"
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
void logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
source: 'Logs',
|
||||
});
|
||||
safeNavigate(ROUTES.LOGS_EXPLORER, {
|
||||
@@ -362,7 +365,7 @@ export default function Home(): JSX.Element {
|
||||
}}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter') {
|
||||
logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
void logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
source: 'Logs',
|
||||
});
|
||||
history.push(ROUTES.LOGS_EXPLORER);
|
||||
@@ -396,7 +399,7 @@ export default function Home(): JSX.Element {
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
void logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
source: 'Traces',
|
||||
});
|
||||
safeNavigate(ROUTES.TRACES_EXPLORER, {
|
||||
@@ -405,7 +408,7 @@ export default function Home(): JSX.Element {
|
||||
}}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter') {
|
||||
logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
void logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
source: 'Traces',
|
||||
});
|
||||
history.push(ROUTES.TRACES_EXPLORER);
|
||||
@@ -439,7 +442,7 @@ export default function Home(): JSX.Element {
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
void logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
source: 'Metrics',
|
||||
});
|
||||
safeNavigate(ROUTES.METRICS_EXPLORER, {
|
||||
@@ -448,7 +451,7 @@ export default function Home(): JSX.Element {
|
||||
}}
|
||||
onKeyDown={(e): void => {
|
||||
if (e.key === 'Enter') {
|
||||
logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
void logEvent('Homepage: Ingestion Active Explore clicked', {
|
||||
source: 'Metrics',
|
||||
});
|
||||
history.push(ROUTES.METRICS_EXPLORER);
|
||||
@@ -496,7 +499,7 @@ export default function Home(): JSX.Element {
|
||||
className="periscope-btn secondary"
|
||||
prefix={<Wrench size={14} />}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
logEvent('Homepage: Explore clicked', {
|
||||
void logEvent('Homepage: Explore clicked', {
|
||||
source: 'Logs',
|
||||
});
|
||||
safeNavigate(ROUTES.LOGS_EXPLORER, {
|
||||
@@ -513,7 +516,7 @@ export default function Home(): JSX.Element {
|
||||
className="periscope-btn secondary"
|
||||
prefix={<Wrench size={14} />}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
logEvent('Homepage: Explore clicked', {
|
||||
void logEvent('Homepage: Explore clicked', {
|
||||
source: 'Traces',
|
||||
});
|
||||
safeNavigate(ROUTES.TRACES_EXPLORER, {
|
||||
@@ -530,7 +533,7 @@ export default function Home(): JSX.Element {
|
||||
className="periscope-btn secondary"
|
||||
prefix={<Wrench size={14} />}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
logEvent('Homepage: Explore clicked', {
|
||||
void logEvent('Homepage: Explore clicked', {
|
||||
source: 'Metrics',
|
||||
});
|
||||
safeNavigate(ROUTES.METRICS_EXPLORER_EXPLORER, {
|
||||
@@ -569,7 +572,7 @@ export default function Home(): JSX.Element {
|
||||
className="periscope-btn secondary"
|
||||
prefix={<Plus size={14} />}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
logEvent('Homepage: Explore clicked', {
|
||||
void logEvent('Homepage: Explore clicked', {
|
||||
source: 'Dashboards',
|
||||
});
|
||||
safeNavigate(ROUTES.ALL_DASHBOARD, {
|
||||
@@ -614,7 +617,7 @@ export default function Home(): JSX.Element {
|
||||
className="periscope-btn secondary"
|
||||
prefix={<Plus size={14} />}
|
||||
onClick={(e: React.MouseEvent): void => {
|
||||
logEvent('Homepage: Explore clicked', {
|
||||
void logEvent('Homepage: Explore clicked', {
|
||||
source: 'Alerts',
|
||||
});
|
||||
safeNavigate(ROUTES.ALERTS_NEW, {
|
||||
|
||||
@@ -21,7 +21,6 @@ const PAGE_SIZE = 20;
|
||||
function MembersSettings(): JSX.Element {
|
||||
const history = useHistory();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const pageParam = parseInt(urlQuery.get('page') ?? '1', 10);
|
||||
const currentPage = Number.isNaN(pageParam) || pageParam < 1 ? 1 : pageParam;
|
||||
|
||||
@@ -146,7 +145,7 @@ function MembersSettings(): JSX.Element {
|
||||
: `Deleted ⎯ ${deletedCount}`;
|
||||
|
||||
const handleInviteComplete = useCallback((): void => {
|
||||
refetchUsers();
|
||||
void refetchUsers();
|
||||
}, [refetchUsers]);
|
||||
|
||||
const handleRowClick = useCallback((member: MemberRow): void => {
|
||||
@@ -158,7 +157,7 @@ function MembersSettings(): JSX.Element {
|
||||
}, []);
|
||||
|
||||
const handleMemberEditComplete = useCallback((): void => {
|
||||
refetchUsers();
|
||||
void refetchUsers();
|
||||
}, [refetchUsers]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -30,7 +30,12 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { GraphTitle, MENU_ITEMS, SERVICE_CHART_ID } from '../constant';
|
||||
import {
|
||||
GraphTitle,
|
||||
MENU_ITEMS,
|
||||
SERVICE_CHART_ID,
|
||||
SERVICE_DETAIL_DRILLDOWN_ENABLED,
|
||||
} from '../constant';
|
||||
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
|
||||
import { Card, GraphContainer, Row } from '../styles';
|
||||
import { Button } from './styles';
|
||||
@@ -206,6 +211,7 @@ function DBCall(): JSX.Element {
|
||||
}}
|
||||
onDragSelect={onDragSelect}
|
||||
version={ENTITY_VERSION_V4}
|
||||
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
@@ -244,6 +250,7 @@ function DBCall(): JSX.Element {
|
||||
}}
|
||||
onDragSelect={onDragSelect}
|
||||
version={ENTITY_VERSION_V4}
|
||||
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
|
||||
@@ -32,7 +32,12 @@ import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { GraphTitle, legend, MENU_ITEMS } from '../constant';
|
||||
import {
|
||||
GraphTitle,
|
||||
legend,
|
||||
MENU_ITEMS,
|
||||
SERVICE_DETAIL_DRILLDOWN_ENABLED,
|
||||
} from '../constant';
|
||||
import { getWidgetQueryBuilder } from '../MetricsApplication.factory';
|
||||
import { Card, GraphContainer, Row } from '../styles';
|
||||
import GraphControlsPanel from './Overview/GraphControlsPanel/GraphControlsPanel';
|
||||
@@ -279,6 +284,7 @@ function External(): JSX.Element {
|
||||
}}
|
||||
onDragSelect={onDragSelect}
|
||||
version={ENTITY_VERSION_V4}
|
||||
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
@@ -322,6 +328,7 @@ function External(): JSX.Element {
|
||||
}}
|
||||
onDragSelect={onDragSelect}
|
||||
version={ENTITY_VERSION_V4}
|
||||
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
@@ -366,6 +373,7 @@ function External(): JSX.Element {
|
||||
}
|
||||
onDragSelect={onDragSelect}
|
||||
version={ENTITY_VERSION_V4}
|
||||
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
@@ -409,6 +417,7 @@ function External(): JSX.Element {
|
||||
}}
|
||||
onDragSelect={onDragSelect}
|
||||
version={ENTITY_VERSION_V4}
|
||||
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
|
||||
/>
|
||||
</GraphContainer>
|
||||
</Card>
|
||||
|
||||
@@ -15,6 +15,7 @@ import DisplayThreshold from 'container/GridCardLayout/WidgetHeader/DisplayThres
|
||||
import {
|
||||
GraphTitle,
|
||||
SERVICE_CHART_ID,
|
||||
SERVICE_DETAIL_DRILLDOWN_ENABLED,
|
||||
} from 'container/MetricsApplication/constant';
|
||||
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||
import { apDexMetricsQueryBuilderQueries } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
|
||||
@@ -105,6 +106,7 @@ function ApDexMetrics({
|
||||
threshold={threshold}
|
||||
isQueryEnabled={isQueryEnabled}
|
||||
version={ENTITY_VERSION_V4}
|
||||
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import Graph from 'container/GridCardLayout/GridCard';
|
||||
import {
|
||||
GraphTitle,
|
||||
SERVICE_CHART_ID,
|
||||
SERVICE_DETAIL_DRILLDOWN_ENABLED,
|
||||
} from 'container/MetricsApplication/constant';
|
||||
import { getWidgetQueryBuilder } from 'container/MetricsApplication/MetricsApplication.factory';
|
||||
import { latency } from 'container/MetricsApplication/MetricsPageQueries/OverviewQueries';
|
||||
@@ -138,6 +139,7 @@ function ServiceOverview({
|
||||
onClickHandler={handleGraphClick('Service')}
|
||||
isQueryEnabled={isQueryEnabled}
|
||||
version={ENTITY_VERSION_V4}
|
||||
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
|
||||
/>
|
||||
)}
|
||||
</GraphContainer>
|
||||
|
||||
@@ -4,6 +4,7 @@ import axios from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
||||
import Graph from 'container/GridCardLayout/GridCard';
|
||||
import { SERVICE_DETAIL_DRILLDOWN_ENABLED } from 'container/MetricsApplication/constant';
|
||||
import { Card, GraphContainer } from 'container/MetricsApplication/styles';
|
||||
import { OnClickPluginOpts } from 'lib/uPlotLib/plugins/onClickPlugin';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
@@ -43,6 +44,7 @@ function TopLevelOperation({
|
||||
onDragSelect={onDragSelect}
|
||||
isQueryEnabled={!topLevelOperationsIsLoading}
|
||||
version={ENTITY_VERSION_V4}
|
||||
enableDrillDown={SERVICE_DETAIL_DRILLDOWN_ENABLED}
|
||||
/>
|
||||
)}
|
||||
</GraphContainer>
|
||||
|
||||
@@ -25,6 +25,8 @@ export const OPERATION_LEGENDS = ['Operations'];
|
||||
|
||||
export const MENU_ITEMS = [MenuItemKeys.View, MenuItemKeys.CreateAlerts];
|
||||
|
||||
export const SERVICE_DETAIL_DRILLDOWN_ENABLED = true;
|
||||
|
||||
export enum FORMULA {
|
||||
ERROR_PERCENTAGE = 'A*100/B',
|
||||
DATABASE_CALLS_AVG_DURATION = 'A/B',
|
||||
|
||||
@@ -127,6 +127,12 @@
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding-bottom: 16px;
|
||||
|
||||
.password-error-text {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--bg-cherry-400);
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-color-picker-trigger {
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Input, Modal } from 'antd';
|
||||
import { Typography } from '@signozhq/ui/typography';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||
import {
|
||||
updateMyPassword,
|
||||
useUpdateMyUserV2,
|
||||
} from 'api/generated/services/users';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
import { Check, FileTerminal, Mail, User } from '@signozhq/icons';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
import { ErrorV2Resp } from 'types/api';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
import '../MySettings.styles.scss';
|
||||
import './UserInfo.styles.scss';
|
||||
|
||||
function UserInfo(): JSX.Element {
|
||||
const { user, org, updateUser } = useAppContext();
|
||||
const { t } = useTranslation(['routes', 'settings', 'common']);
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
const { showErrorModal } = useErrorModal();
|
||||
const { mutateAsync: updateMyUser } = useUpdateMyUserV2();
|
||||
|
||||
const [currentPassword, setCurrentPassword] = useState<string>('');
|
||||
@@ -47,6 +49,8 @@ function UserInfo(): JSX.Element {
|
||||
|
||||
const hideResetPasswordModal = (): void => {
|
||||
setIsResetPasswordModalOpen(false);
|
||||
setCurrentPassword('');
|
||||
setUpdatePassword('');
|
||||
};
|
||||
|
||||
const onChangePasswordClickHandler = async (): Promise<void> => {
|
||||
@@ -57,33 +61,35 @@ function UserInfo(): JSX.Element {
|
||||
newPassword: updatePassword,
|
||||
oldPassword: currentPassword,
|
||||
});
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
toast.success('Password updated successfully');
|
||||
hideResetPasswordModal();
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
setIsLoading(false);
|
||||
notifications.error({
|
||||
message: (error as APIError).error.error.code,
|
||||
description: (error as APIError).error.error.message,
|
||||
});
|
||||
try {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
} catch (apiError) {
|
||||
showErrorModal(apiError as APIError);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const passwordsMatch =
|
||||
currentPassword.length > 0 &&
|
||||
updatePassword.length > 0 &&
|
||||
currentPassword === updatePassword;
|
||||
|
||||
const isResetPasswordDisabled =
|
||||
isLoading ||
|
||||
currentPassword.length === 0 ||
|
||||
updatePassword.length === 0 ||
|
||||
currentPassword === updatePassword;
|
||||
passwordsMatch;
|
||||
|
||||
const onSaveHandler = async (): Promise<void> => {
|
||||
logEvent('Account Settings: Name Updated', {
|
||||
void logEvent('Account Settings: Name Updated', {
|
||||
name: changedName,
|
||||
});
|
||||
logEvent(
|
||||
void logEvent(
|
||||
'Account Settings: Name Updated',
|
||||
{
|
||||
name: changedName,
|
||||
@@ -94,11 +100,7 @@ function UserInfo(): JSX.Element {
|
||||
setIsLoading(true);
|
||||
await updateMyUser({ data: { displayName: changedName } });
|
||||
|
||||
notifications.success({
|
||||
message: t('success', {
|
||||
ns: 'common',
|
||||
}),
|
||||
});
|
||||
toast.success('Name updated successfully');
|
||||
updateUser({
|
||||
...user,
|
||||
displayName: changedName,
|
||||
@@ -106,10 +108,11 @@ function UserInfo(): JSX.Element {
|
||||
setIsLoading(false);
|
||||
hideUpdateNameModal();
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
try {
|
||||
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||
} catch (apiError) {
|
||||
showErrorModal(apiError as APIError);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
@@ -166,7 +169,7 @@ function UserInfo(): JSX.Element {
|
||||
type="primary"
|
||||
icon={<Check size={16} />}
|
||||
onClick={onSaveHandler}
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
data-testid="update-name-btn"
|
||||
>
|
||||
Update name
|
||||
@@ -178,7 +181,11 @@ function UserInfo(): JSX.Element {
|
||||
<Input
|
||||
placeholder="e.g. John Doe"
|
||||
value={changedName}
|
||||
disabled={isLoading}
|
||||
onChange={(e): void => setChangedName(e.target.value)}
|
||||
onPressEnter={(): void => {
|
||||
void onSaveHandler();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
@@ -188,6 +195,7 @@ function UserInfo(): JSX.Element {
|
||||
title={<span className="title">Reset password</span>}
|
||||
open={isResetPasswordModalOpen}
|
||||
closable
|
||||
destroyOnClose
|
||||
onCancel={hideResetPasswordModal}
|
||||
footer={[
|
||||
<Button
|
||||
@@ -197,7 +205,8 @@ function UserInfo(): JSX.Element {
|
||||
}`}
|
||||
icon={<Check size={16} />}
|
||||
onClick={onChangePasswordClickHandler}
|
||||
disabled={isLoading || isResetPasswordDisabled}
|
||||
loading={isLoading}
|
||||
disabled={isResetPasswordDisabled}
|
||||
data-testid="reset-password-btn"
|
||||
>
|
||||
Reset password
|
||||
@@ -218,6 +227,11 @@ function UserInfo(): JSX.Element {
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
visibilityToggle
|
||||
onPressEnter={(): void => {
|
||||
if (!isResetPasswordDisabled) {
|
||||
void onChangePasswordClickHandler();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -235,7 +249,18 @@ function UserInfo(): JSX.Element {
|
||||
type="password"
|
||||
autoComplete="off"
|
||||
visibilityToggle={false}
|
||||
status={passwordsMatch ? 'error' : ''}
|
||||
onPressEnter={(): void => {
|
||||
if (!isResetPasswordDisabled) {
|
||||
void onChangePasswordClickHandler();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{passwordsMatch && (
|
||||
<span className="password-error-text">
|
||||
New password must be different from current password
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -8,11 +8,23 @@ import {
|
||||
waitFor,
|
||||
within,
|
||||
} from 'tests/test-utils';
|
||||
import APIError from 'types/api/error';
|
||||
import { toast } from '@signozhq/ui/sonner';
|
||||
|
||||
const toggleThemeFunction = jest.fn();
|
||||
const logEventFunction = jest.fn();
|
||||
const copyToClipboardFn = jest.fn();
|
||||
const editUserFn = jest.fn();
|
||||
const updateMyPasswordFn = jest.fn();
|
||||
const showErrorModalFn = jest.fn();
|
||||
|
||||
jest.mock('@signozhq/ui/sonner', () => ({
|
||||
...jest.requireActual('@signozhq/ui/sonner'),
|
||||
toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('react-use', () => ({
|
||||
__esModule: true,
|
||||
@@ -24,12 +36,21 @@ jest.mock('react-use', () => ({
|
||||
|
||||
jest.mock('api/generated/services/users', () => ({
|
||||
...jest.requireActual('api/generated/services/users'),
|
||||
updateMyPassword: (...args: unknown[]): Promise<unknown> =>
|
||||
updateMyPasswordFn(...args),
|
||||
useUpdateMyUserV2: jest.fn(() => ({
|
||||
mutateAsync: (...args: unknown[]): Promise<unknown> => editUserFn(...args),
|
||||
isLoading: false,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('providers/ErrorModalProvider', () => ({
|
||||
...jest.requireActual('providers/ErrorModalProvider'),
|
||||
useErrorModal: jest.fn(() => ({
|
||||
showErrorModal: showErrorModalFn,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/useDarkMode', () => ({
|
||||
__esModule: true,
|
||||
useIsDarkMode: jest.fn(() => true),
|
||||
@@ -65,12 +86,12 @@ const NEW_PASSWORD_TEST_ID = 'new-password-textbox';
|
||||
const UPDATE_NAME_BUTTON_TEST_ID = 'update-name-btn';
|
||||
const RESET_PASSWORD_BUTTON_TEST_ID = 'reset-password-btn';
|
||||
const UPDATE_NAME_BUTTON_TEXT = 'Update name';
|
||||
const PASSWORD_VALIDATION_MESSAGE_TEST_ID = 'password-validation-message';
|
||||
|
||||
describe('MySettings Flows', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
editUserFn.mockResolvedValue({});
|
||||
updateMyPasswordFn.mockResolvedValue({});
|
||||
render(<MySettingsContainer />);
|
||||
});
|
||||
|
||||
@@ -152,9 +173,7 @@ describe('MySettings Flows', () => {
|
||||
fireEvent.click(modalUpdateNameButton);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(successNotification).toHaveBeenCalledWith({
|
||||
message: 'success',
|
||||
}),
|
||||
expect(toast.success).toHaveBeenCalledWith('Name updated successfully'),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -181,22 +200,131 @@ describe('MySettings Flows', () => {
|
||||
expect(screen.getByTestId(NEW_PASSWORD_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should display validation error if password is less than 8 characters', async () => {
|
||||
it('Should show inline error when new password matches current password', async () => {
|
||||
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
|
||||
fireEvent.click(resetPasswordButtons[0]);
|
||||
|
||||
const currentPasswordTextbox = screen.getByTestId(CURRENT_PASSWORD_TEST_ID);
|
||||
act(() => {
|
||||
fireEvent.change(currentPasswordTextbox, { target: { value: '123' } });
|
||||
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
|
||||
target: { value: 'samePassword1' },
|
||||
});
|
||||
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
|
||||
target: { value: 'samePassword1' },
|
||||
});
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByText('New password must be different from current password'),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId(RESET_PASSWORD_BUTTON_TEST_ID)).toBeDisabled();
|
||||
});
|
||||
|
||||
it('Should hide inline error when passwords are changed to be different', async () => {
|
||||
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
|
||||
fireEvent.click(resetPasswordButtons[0]);
|
||||
|
||||
act(() => {
|
||||
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
|
||||
target: { value: 'samePassword1' },
|
||||
});
|
||||
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
|
||||
target: { value: 'samePassword1' },
|
||||
});
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
|
||||
target: { value: 'differentPassword1' },
|
||||
});
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByText('New password must be different from current password'),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should show error modal when password reset API returns an error', async () => {
|
||||
updateMyPasswordFn.mockRejectedValue(
|
||||
new Error('Current password is incorrect'),
|
||||
);
|
||||
|
||||
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
|
||||
fireEvent.click(resetPasswordButtons[0]);
|
||||
|
||||
act(() => {
|
||||
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
|
||||
target: { value: 'oldPassword1' },
|
||||
});
|
||||
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
|
||||
target: { value: 'newPassword1' },
|
||||
});
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId(RESET_PASSWORD_BUTTON_TEST_ID));
|
||||
|
||||
await waitFor(() => {
|
||||
// Use getByTestId for the validation message (if present in your modal/component)
|
||||
if (screen.queryByTestId(PASSWORD_VALIDATION_MESSAGE_TEST_ID)) {
|
||||
expect(
|
||||
screen.getByTestId(PASSWORD_VALIDATION_MESSAGE_TEST_ID),
|
||||
).toBeInTheDocument();
|
||||
}
|
||||
expect(showErrorModalFn).toHaveBeenCalledWith(expect.any(APIError));
|
||||
});
|
||||
});
|
||||
|
||||
it('Should show success toast and close modal on successful password reset', async () => {
|
||||
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
|
||||
fireEvent.click(resetPasswordButtons[0]);
|
||||
|
||||
act(() => {
|
||||
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
|
||||
target: { value: 'oldPassword1' },
|
||||
});
|
||||
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
|
||||
target: { value: 'newPassword1' },
|
||||
});
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId(RESET_PASSWORD_BUTTON_TEST_ID));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith('Password updated successfully');
|
||||
expect(
|
||||
screen.queryByTestId(CURRENT_PASSWORD_TEST_ID),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('Should clear password fields when modal is cancelled', async () => {
|
||||
const resetPasswordButtons = screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT);
|
||||
fireEvent.click(resetPasswordButtons[0]);
|
||||
|
||||
act(() => {
|
||||
fireEvent.change(screen.getByTestId(CURRENT_PASSWORD_TEST_ID), {
|
||||
target: { value: 'somePassword' },
|
||||
});
|
||||
fireEvent.change(screen.getByTestId(NEW_PASSWORD_TEST_ID), {
|
||||
target: { value: 'otherPassword' },
|
||||
});
|
||||
});
|
||||
|
||||
expect(screen.getByTestId(CURRENT_PASSWORD_TEST_ID)).toHaveValue(
|
||||
'somePassword',
|
||||
);
|
||||
|
||||
// Close the modal
|
||||
const closeButton = document.querySelector(
|
||||
'.reset-password-modal .ant-modal-close',
|
||||
) as HTMLElement;
|
||||
fireEvent.click(closeButton);
|
||||
|
||||
// Reopen the modal
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByTestId(CURRENT_PASSWORD_TEST_ID),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getAllByText(RESET_PASSWORD_BUTTON_TEXT)[0]);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId(CURRENT_PASSWORD_TEST_ID)).toHaveValue('');
|
||||
expect(screen.getByTestId(NEW_PASSWORD_TEST_ID)).toHaveValue('');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.STARTED}`,
|
||||
{},
|
||||
);
|
||||
@@ -253,7 +253,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
setSelectedFramework(null);
|
||||
setSelectedEnvironment(null);
|
||||
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_SELECTED}`,
|
||||
{
|
||||
dataSource: dataSource.label,
|
||||
@@ -276,7 +276,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
};
|
||||
|
||||
const handleSelectFramework = (option: any): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.FRAMEWORK_SELECTED}`,
|
||||
{
|
||||
dataSource: selectedDataSource?.label,
|
||||
@@ -309,7 +309,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
selectedEnvironment: any,
|
||||
baseURL?: string,
|
||||
): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.ENVIRONMENT_SELECTED}`,
|
||||
{
|
||||
dataSource: selectedDataSource?.label,
|
||||
@@ -351,7 +351,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
groupDataSourcesByTags(filteredDataSources as Entity[]),
|
||||
);
|
||||
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_SEARCHED}`,
|
||||
{
|
||||
searchedDataSource: query,
|
||||
@@ -485,7 +485,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
};
|
||||
|
||||
const handleShowInviteTeamMembersModal = (): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.INVITE_TEAM_MEMBER_BUTTON_CLICKED}`,
|
||||
{
|
||||
dataSource: selectedDataSource?.label,
|
||||
@@ -498,7 +498,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
};
|
||||
|
||||
const handleSubmitDataSourceRequest = (): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_REQUESTED}`,
|
||||
{
|
||||
requestedDataSource: dataSourceRequest,
|
||||
@@ -513,7 +513,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
};
|
||||
|
||||
const handleRaiseRequest = (): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.DATA_SOURCE_REQUESTED}`,
|
||||
{
|
||||
requestedDataSource: searchQuery,
|
||||
@@ -635,7 +635,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
size={14}
|
||||
className="onboarding-header-container-close-icon"
|
||||
onClick={(e): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.CLOSE_ONBOARDING_CLICKED}`,
|
||||
{
|
||||
currentPage: setupStepItems[currentStep]?.title || '',
|
||||
@@ -970,7 +970,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
disabled={!selectedDataSource}
|
||||
shape="round"
|
||||
onClick={(e): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.CONFIGURED_PRODUCT}`,
|
||||
{
|
||||
dataSource: selectedDataSource?.label,
|
||||
@@ -1038,7 +1038,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
type="default"
|
||||
shape="round"
|
||||
onClick={(): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BACK_BUTTON_CLICKED}`,
|
||||
{
|
||||
dataSource: selectedDataSource?.label,
|
||||
@@ -1057,7 +1057,7 @@ function OnboardingAddDataSource(): JSX.Element {
|
||||
type="primary"
|
||||
shape="round"
|
||||
onClick={(e): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.BASE}: ${ONBOARDING_V3_ANALYTICS_EVENTS_MAP?.CONTINUE_BUTTON_CLICKED}`,
|
||||
{
|
||||
dataSource: selectedDataSource?.label,
|
||||
|
||||
@@ -75,7 +75,7 @@ function AuthDomain(): JSX.Element {
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.success('Domain deleted successfully');
|
||||
refetchAuthDomainListResponse();
|
||||
void refetchAuthDomainListResponse();
|
||||
hideDeleteModal();
|
||||
},
|
||||
onError: (error) => {
|
||||
|
||||
@@ -169,9 +169,10 @@ describe('drilldownUtils', () => {
|
||||
|
||||
// Verify transformations were applied
|
||||
if (filterExpression) {
|
||||
// Rule 2: operation → name
|
||||
expect(filterExpression).toContain(`name = 'GET'`);
|
||||
// `operation` rewrites to `name` via source-side pass, then `name`
|
||||
// is dropped by the logs target-side pass (logs has no span-name).
|
||||
expect(filterExpression).not.toContain(`operation = 'GET'`);
|
||||
expect(filterExpression).not.toContain(`name = 'GET'`);
|
||||
|
||||
// Rule 3: span.kind → kind
|
||||
expect(filterExpression).toContain(`${spanKindKey} = '${spanKindServer}'`);
|
||||
@@ -262,8 +263,9 @@ describe('drilldownUtils', () => {
|
||||
const filterExpression = result?.builder.queryData[0]?.filter?.expression;
|
||||
|
||||
if (filterExpression) {
|
||||
// All transformations should be applied
|
||||
expect(filterExpression).toContain(`name = 'POST'`);
|
||||
// `operation` rewrites to `name` then drops for logs target.
|
||||
expect(filterExpression).not.toContain(`operation = 'POST'`);
|
||||
expect(filterExpression).not.toContain(`name = 'POST'`);
|
||||
expect(filterExpression).toContain(`${spanKindKey} = '${spanKindClient}'`);
|
||||
expect(filterExpression).toContain(`status_code_string = 'Error'`);
|
||||
expect(filterExpression).toContain(`http.status_code = 500`);
|
||||
@@ -410,8 +412,9 @@ describe('drilldownUtils', () => {
|
||||
const filterExpression = result?.builder.queryData[0]?.filter?.expression;
|
||||
|
||||
if (filterExpression) {
|
||||
// Transformed attributes
|
||||
expect(filterExpression).toContain(`name = 'GET'`);
|
||||
// `operation` rewrites to `name` then drops for logs target.
|
||||
expect(filterExpression).not.toContain(`operation = 'GET'`);
|
||||
expect(filterExpression).not.toContain(`name = 'GET'`);
|
||||
expect(filterExpression).toContain(`${spanKindKey} = '${spanKindServer}'`);
|
||||
|
||||
// Preserved non-metric attributes
|
||||
@@ -499,4 +502,189 @@ describe('drilldownUtils', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getViewQuery target-aware sanitisation (serviceName / name)', () => {
|
||||
const makeQuery = (
|
||||
expression: string,
|
||||
dataSource: 'traces' | 'logs' | 'metrics' = 'traces',
|
||||
): Query => ({
|
||||
id: 'src-query',
|
||||
queryType: 'builder' as any,
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
queryName: 'src',
|
||||
dataSource: dataSource as any,
|
||||
aggregations: [{ metricName: 'non_apm_metric' }] as any,
|
||||
groupBy: [],
|
||||
expression: '',
|
||||
disabled: false,
|
||||
functions: [],
|
||||
legend: '',
|
||||
having: [],
|
||||
limit: null,
|
||||
stepInterval: undefined,
|
||||
orderBy: [],
|
||||
filter: { expression },
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
});
|
||||
|
||||
it('rewrites serviceName -> service.name when drilling to logs', () => {
|
||||
const result = getViewQuery(
|
||||
makeQuery(`serviceName = 'svc'`),
|
||||
[],
|
||||
'view_logs',
|
||||
'src',
|
||||
);
|
||||
const expr = result?.builder.queryData[0]?.filter?.expression || '';
|
||||
expect(expr).toContain(`service.name = 'svc'`);
|
||||
expect(expr).not.toContain('serviceName');
|
||||
});
|
||||
|
||||
it('rewrites serviceName -> service.name when drilling to traces', () => {
|
||||
const result = getViewQuery(
|
||||
makeQuery(`serviceName = 'svc'`),
|
||||
[],
|
||||
'view_traces',
|
||||
'src',
|
||||
);
|
||||
const expr = result?.builder.queryData[0]?.filter?.expression || '';
|
||||
expect(expr).toContain(`service.name = 'svc'`);
|
||||
expect(expr).not.toContain('serviceName');
|
||||
});
|
||||
|
||||
it('drops `name` clause when drilling to logs', () => {
|
||||
const result = getViewQuery(
|
||||
makeQuery(`name = 'GET /api'`),
|
||||
[],
|
||||
'view_logs',
|
||||
'src',
|
||||
);
|
||||
const expr = result?.builder.queryData[0]?.filter?.expression || '';
|
||||
expect(expr).not.toContain(`name = 'GET /api'`);
|
||||
});
|
||||
|
||||
it('keeps `name` clause when drilling to traces', () => {
|
||||
const result = getViewQuery(
|
||||
makeQuery(`name = 'GET /api'`),
|
||||
[],
|
||||
'view_traces',
|
||||
'src',
|
||||
);
|
||||
const expr = result?.builder.queryData[0]?.filter?.expression || '';
|
||||
expect(expr).toContain(`name = 'GET /api'`);
|
||||
});
|
||||
|
||||
it('combined: drilling to logs rewrites serviceName and drops name', () => {
|
||||
const result = getViewQuery(
|
||||
makeQuery(`serviceName = 'svc' AND name = 'GET /api'`),
|
||||
[],
|
||||
'view_logs',
|
||||
'src',
|
||||
);
|
||||
const expr = result?.builder.queryData[0]?.filter?.expression || '';
|
||||
expect(expr).toContain(`service.name = 'svc'`);
|
||||
expect(expr).not.toContain('serviceName');
|
||||
expect(expr).not.toContain(`name = 'GET /api'`);
|
||||
});
|
||||
|
||||
it('combined: drilling to traces rewrites serviceName and keeps name', () => {
|
||||
const result = getViewQuery(
|
||||
makeQuery(`serviceName = 'svc' AND name = 'GET /api'`),
|
||||
[],
|
||||
'view_traces',
|
||||
'src',
|
||||
);
|
||||
const expr = result?.builder.queryData[0]?.filter?.expression || '';
|
||||
expect(expr).toContain(`service.name = 'svc'`);
|
||||
expect(expr).toContain(`name = 'GET /api'`);
|
||||
expect(expr).not.toContain('serviceName');
|
||||
});
|
||||
|
||||
it('metric-APM source -> traces target preserves existing operation -> name rewrite', () => {
|
||||
const metricsQuery: Query = {
|
||||
id: 'apm-metrics',
|
||||
queryType: 'builder' as any,
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
queryName: 'm',
|
||||
dataSource: 'metrics' as any,
|
||||
aggregations: [{ metricName: 'signoz_calls_total' }] as any,
|
||||
groupBy: [],
|
||||
expression: '',
|
||||
disabled: false,
|
||||
functions: [],
|
||||
legend: '',
|
||||
having: [],
|
||||
limit: null,
|
||||
stepInterval: undefined,
|
||||
orderBy: [],
|
||||
filter: { expression: `operation = 'GET'` },
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
};
|
||||
const result = getViewQuery(metricsQuery, [], 'view_traces', 'm');
|
||||
const expr = result?.builder.queryData[0]?.filter?.expression || '';
|
||||
expect(expr).toContain(`name = 'GET'`);
|
||||
expect(expr).not.toContain(`operation = 'GET'`);
|
||||
});
|
||||
|
||||
it('metric-APM source -> logs target: operation rewrites to name, then dropped', () => {
|
||||
const metricsQuery: Query = {
|
||||
id: 'apm-metrics',
|
||||
queryType: 'builder' as any,
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
queryName: 'm',
|
||||
dataSource: 'metrics' as any,
|
||||
aggregations: [{ metricName: 'signoz_calls_total' }] as any,
|
||||
groupBy: [],
|
||||
expression: '',
|
||||
disabled: false,
|
||||
functions: [],
|
||||
legend: '',
|
||||
having: [],
|
||||
limit: null,
|
||||
stepInterval: undefined,
|
||||
orderBy: [],
|
||||
filter: { expression: `operation = 'GET'` },
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
queryTraceOperator: [],
|
||||
},
|
||||
promql: [],
|
||||
clickhouse_sql: [],
|
||||
};
|
||||
const result = getViewQuery(metricsQuery, [], 'view_logs', 'm');
|
||||
const expr = result?.builder.queryData[0]?.filter?.expression || '';
|
||||
expect(expr).not.toContain(`operation = 'GET'`);
|
||||
expect(expr).not.toContain(`name = 'GET'`);
|
||||
});
|
||||
|
||||
it('drilling to metrics does not apply target-side sanitisation', () => {
|
||||
const result = getViewQuery(
|
||||
makeQuery(`serviceName = 'svc' AND name = 'GET /api'`),
|
||||
[],
|
||||
'view_metrics',
|
||||
'src',
|
||||
);
|
||||
const expr = result?.builder.queryData[0]?.filter?.expression || '';
|
||||
expect(expr).toContain('serviceName');
|
||||
expect(expr).toContain(`name = 'GET /api'`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,8 +7,10 @@ import {
|
||||
import ROUTES from 'constants/routes';
|
||||
import { isApmMetric } from 'container/PanelWrapper/utils';
|
||||
import {
|
||||
applyMappingsToExpression,
|
||||
DRILLDOWN_TO_LOGS_MAPPINGS,
|
||||
DRILLDOWN_TO_TRACES_MAPPINGS,
|
||||
METRIC_TO_LOGS_TRACES_MAPPINGS,
|
||||
replaceKeysAndValuesInExpression,
|
||||
} from 'container/QueryTable/Drilldown/metricsCorrelationUtils';
|
||||
import cloneDeep from 'lodash-es/cloneDeep';
|
||||
import {
|
||||
@@ -347,27 +349,41 @@ export const getViewQuery = (
|
||||
newQuery.builder.queryData[0].filter = newFilterExpression;
|
||||
|
||||
try {
|
||||
// ===========================================
|
||||
// TEMP LOGIC - TO BE REMOVED LATER
|
||||
// ===========================================
|
||||
// Apply metric-to-logs/traces transformations
|
||||
// Drill-down filter sanitisation. Two stages:
|
||||
// 1. Source-side: rewrite metric-APM-specific keys (operation, span.kind,
|
||||
// status.code) so they map onto trace/log columns.
|
||||
// 2. Target-side: normalise legacy keys to OTel-canonical (`serviceName`
|
||||
// -> `service.name`) and drop keys with no equivalent in the target
|
||||
// datasource (e.g. `name` for logs).
|
||||
let expression = newFilterExpression?.expression || '';
|
||||
|
||||
const specificQuery = getQueryData(query, queryName);
|
||||
const isMetricQuery = specificQuery?.dataSource === 'metrics';
|
||||
const metricName = (specificQuery?.aggregations?.[0] as MetricAggregation)
|
||||
?.metricName;
|
||||
|
||||
if (isMetricQuery && isApmMetric(metricName || '')) {
|
||||
const transformedExpression = replaceKeysAndValuesInExpression(
|
||||
newFilterExpression?.expression || '',
|
||||
expression = applyMappingsToExpression(
|
||||
expression,
|
||||
METRIC_TO_LOGS_TRACES_MAPPINGS,
|
||||
);
|
||||
newQuery.builder.queryData[0].filter = {
|
||||
expression: transformedExpression || '',
|
||||
};
|
||||
}
|
||||
// ===========================================
|
||||
|
||||
if (key === 'view_logs') {
|
||||
expression = applyMappingsToExpression(
|
||||
expression,
|
||||
DRILLDOWN_TO_LOGS_MAPPINGS,
|
||||
);
|
||||
} else if (key === 'view_traces') {
|
||||
expression = applyMappingsToExpression(
|
||||
expression,
|
||||
DRILLDOWN_TO_TRACES_MAPPINGS,
|
||||
);
|
||||
}
|
||||
|
||||
newQuery.builder.queryData[0].filter = { expression };
|
||||
} catch (error) {
|
||||
console.error('Error transforming metrics to logs/traces:', error);
|
||||
console.error('Error sanitising drilldown filter expression:', error);
|
||||
}
|
||||
|
||||
return newQuery;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/* eslint-disable sonarjs/cognitive-complexity */
|
||||
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
|
||||
import {
|
||||
formatValueForExpression,
|
||||
removeKeysFromExpression,
|
||||
} from 'components/QueryBuilderV2/utils';
|
||||
import { getOperatorValue } from 'container/QueryBuilder/filters/QueryBuilderSearch/utils';
|
||||
import { IQueryPair } from 'types/antlrQueryTypes';
|
||||
import { extractQueryPairs } from 'utils/queryContextUtils';
|
||||
@@ -8,7 +11,7 @@ import { isFunctionOperator, isNonValueOperator } from 'utils/tokenUtils';
|
||||
|
||||
type KeyValueMapping = {
|
||||
attribute: string;
|
||||
newAttribute: string;
|
||||
newAttribute: string | null;
|
||||
valueMappings: Record<string, string>;
|
||||
};
|
||||
|
||||
@@ -40,8 +43,33 @@ export const METRIC_TO_LOGS_TRACES_MAPPINGS: KeyValueMapping[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const DRILLDOWN_TO_LOGS_MAPPINGS: KeyValueMapping[] = [
|
||||
{
|
||||
attribute: 'serviceName',
|
||||
newAttribute: 'service.name',
|
||||
valueMappings: {},
|
||||
},
|
||||
{
|
||||
attribute: 'name',
|
||||
newAttribute: null,
|
||||
valueMappings: {},
|
||||
},
|
||||
];
|
||||
|
||||
export const DRILLDOWN_TO_TRACES_MAPPINGS: KeyValueMapping[] = [
|
||||
{
|
||||
attribute: 'serviceName',
|
||||
newAttribute: 'service.name',
|
||||
valueMappings: {},
|
||||
},
|
||||
];
|
||||
|
||||
// Logic for rewriting key/values in an expression using provided mappings.
|
||||
function modifyKeyVal(pair: IQueryPair, mapping: KeyValueMapping): string {
|
||||
// Callers must pre-filter mappings to ensure newAttribute is non-null.
|
||||
function modifyKeyVal(
|
||||
pair: IQueryPair,
|
||||
mapping: KeyValueMapping & { newAttribute: string },
|
||||
): string {
|
||||
const newKey = mapping.newAttribute;
|
||||
const op = pair.operator;
|
||||
|
||||
@@ -107,8 +135,18 @@ export function replaceKeysAndValuesInExpression(
|
||||
return expression;
|
||||
}
|
||||
|
||||
const attributeToMapping = new Map<string, KeyValueMapping>(
|
||||
mappingList.map((m) => [m.attribute.trim().toLowerCase(), m]),
|
||||
// Only rewrite mappings (newAttribute non-null) are processed here.
|
||||
// Drops are handled separately by applyMappingsToExpression via removeKeysFromExpression.
|
||||
const attributeToMapping = new Map<
|
||||
string,
|
||||
KeyValueMapping & { newAttribute: string }
|
||||
>(
|
||||
mappingList
|
||||
.filter(
|
||||
(m): m is KeyValueMapping & { newAttribute: string } =>
|
||||
m.newAttribute !== null,
|
||||
)
|
||||
.map((m) => [m.attribute.trim().toLowerCase(), m]),
|
||||
);
|
||||
|
||||
const pairs: IQueryPair[] = extractQueryPairs(expression);
|
||||
@@ -179,3 +217,26 @@ export function replaceKeysAndValuesInExpression(
|
||||
|
||||
return resultParts.join('');
|
||||
}
|
||||
|
||||
// Apply a list of mappings to a filter expression. Rewrites are applied first
|
||||
// (newAttribute is a string), then drops (newAttribute is null) via the
|
||||
// ANTLR-parser-based removeKeysFromExpression which handles AND/OR/NOT/paren
|
||||
// elision correctly.
|
||||
export function applyMappingsToExpression(
|
||||
expression: string,
|
||||
mappings: KeyValueMapping[],
|
||||
): string {
|
||||
if (!expression || !mappings || mappings.length === 0) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
const dropKeys = mappings
|
||||
.filter((m) => m.newAttribute === null)
|
||||
.map((m) => m.attribute);
|
||||
|
||||
let result = replaceKeysAndValuesInExpression(expression, mappings);
|
||||
if (dropKeys.length > 0) {
|
||||
result = removeKeysFromExpression(result, dropKeys);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ export function getAppContextMockState(
|
||||
userPreferences: null,
|
||||
hostsData: null,
|
||||
isLoggedIn: false,
|
||||
isPreflightLoading: false,
|
||||
org: null,
|
||||
isFetchingUser: false,
|
||||
isFetchingActiveLicense: false,
|
||||
|
||||
@@ -1120,6 +1120,7 @@
|
||||
|
||||
.user-settings-dropdown-logout-section {
|
||||
color: var(--danger-background);
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +408,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
);
|
||||
|
||||
const handleReorderShortcutNavItems = (): void => {
|
||||
logEvent('Sidebar V2: Save shortcuts clicked', {
|
||||
void logEvent('Sidebar V2: Save shortcuts clicked', {
|
||||
shortcuts: tempPinnedMenuItems.map((item) => item.key),
|
||||
});
|
||||
setPinnedMenuItems(tempPinnedMenuItems);
|
||||
@@ -436,7 +436,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
const isWorkspaceBlocked = trialInfo?.workSpaceBlock || false;
|
||||
|
||||
const onClickGetStarted = (event: MouseEvent): void => {
|
||||
logEvent('Sidebar: Menu clicked', {
|
||||
void logEvent('Sidebar: Menu clicked', {
|
||||
menuRoute: '/get-started',
|
||||
menuLabel: 'Get Started',
|
||||
});
|
||||
@@ -651,7 +651,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
} else if (item) {
|
||||
onClickHandler(item?.key as string, event);
|
||||
}
|
||||
logEvent('Sidebar V2: Menu clicked', {
|
||||
void logEvent('Sidebar V2: Menu clicked', {
|
||||
menuRoute: item?.key,
|
||||
menuLabel: item?.label,
|
||||
});
|
||||
@@ -794,7 +794,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
onTogglePin={
|
||||
allowPin
|
||||
? (item): void => {
|
||||
logEvent(
|
||||
void logEvent(
|
||||
`Sidebar V2: Menu item ${item.isPinned ? 'unpinned' : 'pinned'}`,
|
||||
{
|
||||
menuRoute: item.key,
|
||||
@@ -841,7 +841,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
const event = (info as SidebarItem & { domEvent?: MouseEvent }).domEvent;
|
||||
|
||||
if (item && !('type' in item)) {
|
||||
logEvent('Help Popover: Item clicked', {
|
||||
void logEvent('Help Popover: Item clicked', {
|
||||
menuRoute: item.key,
|
||||
menuLabel: String(item.label),
|
||||
});
|
||||
@@ -890,7 +890,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
menuLabel = item.label;
|
||||
}
|
||||
|
||||
logEvent('Settings Popover: Item clicked', {
|
||||
void logEvent('Settings Popover: Item clicked', {
|
||||
menuRoute: item?.key,
|
||||
menuLabel,
|
||||
});
|
||||
@@ -927,7 +927,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
}
|
||||
break;
|
||||
case 'logout':
|
||||
Logout();
|
||||
void Logout();
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -1081,7 +1081,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
<div
|
||||
className="nav-section-title-icon reorder"
|
||||
onClick={(): void => {
|
||||
logEvent('Sidebar V2: Manage shortcuts clicked', {});
|
||||
void logEvent('Sidebar V2: Manage shortcuts clicked', {});
|
||||
setIsReorderShortcutNavItemsModalOpen(true);
|
||||
}}
|
||||
>
|
||||
@@ -1128,7 +1128,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
return;
|
||||
}
|
||||
const newCollapsedState = !isMoreMenuCollapsed;
|
||||
logEvent('Sidebar V2: More menu clicked', {
|
||||
void logEvent('Sidebar V2: More menu clicked', {
|
||||
action: isMoreMenuCollapsed ? 'expand' : 'collapse',
|
||||
});
|
||||
setIsMoreMenuCollapsed(newCollapsedState);
|
||||
@@ -1234,14 +1234,14 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
open={isReorderShortcutNavItemsModalOpen}
|
||||
closable
|
||||
onCancel={(): void => {
|
||||
logEvent('Sidebar V2: Manage shortcuts dismissed', {});
|
||||
void logEvent('Sidebar V2: Manage shortcuts dismissed', {});
|
||||
hideReorderShortcutNavItemsModal();
|
||||
}}
|
||||
footer={[
|
||||
<Button
|
||||
key="cancel"
|
||||
onClick={(): void => {
|
||||
logEvent('Sidebar V2: Manage shortcuts dismissed', {});
|
||||
void logEvent('Sidebar V2: Manage shortcuts dismissed', {});
|
||||
hideReorderShortcutNavItemsModal();
|
||||
}}
|
||||
className="periscope-btn cancel-btn secondary-btn"
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { MenuProps } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
ArrowUpRight,
|
||||
BarChart,
|
||||
@@ -35,15 +37,13 @@ import {
|
||||
Users,
|
||||
Binoculars,
|
||||
} from '@signozhq/icons';
|
||||
import { Style } from '@signozhq/design-tokens';
|
||||
import { MenuProps } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
|
||||
import {
|
||||
SecondaryMenuItemKey,
|
||||
SettingsNavSection,
|
||||
SidebarItem,
|
||||
} from './sideNav.types';
|
||||
import { Style } from '@signozhq/design-tokens';
|
||||
|
||||
export const getStartedMenuItem = {
|
||||
key: ROUTES.GET_STARTED,
|
||||
|
||||
@@ -48,11 +48,10 @@ describe('useAuthZ', () => {
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(result.current.permissions).toStrictEqual(expectedResponse);
|
||||
});
|
||||
|
||||
it('should handle API errors', async () => {
|
||||
it('should return error and null permissions when API errors', async () => {
|
||||
const permission = buildPermission('read', 'role:*');
|
||||
|
||||
server.use(
|
||||
@@ -191,7 +190,6 @@ describe('useAuthZ', () => {
|
||||
});
|
||||
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
expect(result.current.error).toBeNull();
|
||||
expect(result.current.permissions).toStrictEqual({});
|
||||
});
|
||||
|
||||
84
frontend/src/lib/__tests__/getLabelName.test.ts
Normal file
84
frontend/src/lib/__tests__/getLabelName.test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import getLabelName from 'lib/getLabelName';
|
||||
|
||||
describe('getLabelName', () => {
|
||||
describe('with a legend template', () => {
|
||||
it('substitutes a single variable that exists on the series', () => {
|
||||
const result = getLabelName(
|
||||
{ 'service.name': 'frontend' },
|
||||
'A',
|
||||
'{{service.name}}',
|
||||
);
|
||||
expect(result).toBe('frontend');
|
||||
});
|
||||
|
||||
it('substitutes a template with surrounding literal text', () => {
|
||||
const result = getLabelName(
|
||||
{ 'service.name': 'frontend' },
|
||||
'A',
|
||||
'rate for {{service.name}}',
|
||||
);
|
||||
expect(result).toBe('rate for frontend');
|
||||
});
|
||||
|
||||
it('substitutes multiple variables when all are present', () => {
|
||||
const result = getLabelName(
|
||||
{ 'service.name': 'frontend', 'http.target': 'GET /api' },
|
||||
'A',
|
||||
'{{service.name}} / {{http.target}}',
|
||||
);
|
||||
expect(result).toBe('frontend / GET /api');
|
||||
});
|
||||
|
||||
it('falls back to query name when a referenced variable is missing', () => {
|
||||
const result = getLabelName(
|
||||
{ 'http.target': 'GET /api' },
|
||||
'F1',
|
||||
'{{service.name}}',
|
||||
);
|
||||
expect(result).toBe('F1');
|
||||
});
|
||||
|
||||
it('falls back to query name even if literal text would still render', () => {
|
||||
const result = getLabelName(
|
||||
{ 'http.target': 'GET /api' },
|
||||
'F1',
|
||||
'label = {{label}}',
|
||||
);
|
||||
expect(result).toBe('F1');
|
||||
});
|
||||
|
||||
it('falls back to query name when any of multiple variables is missing', () => {
|
||||
const result = getLabelName(
|
||||
{ 'service.name': 'frontend' },
|
||||
'F1',
|
||||
'{{service.name}} / {{http.target}}',
|
||||
);
|
||||
expect(result).toBe('F1');
|
||||
});
|
||||
|
||||
it('treats a null label value as missing', () => {
|
||||
const result = getLabelName(
|
||||
{ 'service.name': null } as unknown as Record<string, string>,
|
||||
'F1',
|
||||
'{{service.name}}',
|
||||
);
|
||||
expect(result).toBe('F1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('without a legend template', () => {
|
||||
it('returns key="value" pairs for plain labels', () => {
|
||||
const result = getLabelName(
|
||||
{ 'service.name': 'frontend', 'http.target': 'GET /api' },
|
||||
'A',
|
||||
'',
|
||||
);
|
||||
expect(result).toBe('{service.name="frontend",http.target="GET /api"}');
|
||||
});
|
||||
|
||||
it('returns query name when labels are empty', () => {
|
||||
const result = getLabelName({}, 'A', '');
|
||||
expect(result).toBe('A');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -18,6 +18,17 @@ const getLabelName = (
|
||||
|
||||
const results = variables.map((variable) => metric[variable]);
|
||||
|
||||
// Fall back to query name if any `{{var}}` references a label that
|
||||
// isn't on this series — avoids rendering "undefined" in the legend.
|
||||
const hasMissingVariable = variables.some(
|
||||
(variable, index) =>
|
||||
legends.includes(`{{${variable}}}`) &&
|
||||
(results[index] === undefined || results[index] === null),
|
||||
);
|
||||
if (hasMissingVariable) {
|
||||
return query;
|
||||
}
|
||||
|
||||
let endResult = legends;
|
||||
|
||||
variables.forEach((e, index) => {
|
||||
|
||||
@@ -321,7 +321,7 @@ function SettingsPage(): JSX.Element {
|
||||
isDisabled={false}
|
||||
showIcon={false}
|
||||
onClick={(event): void => {
|
||||
logEvent('Settings V2: Menu clicked', {
|
||||
void logEvent('Settings V2: Menu clicked', {
|
||||
menuLabel: item.label,
|
||||
menuRoute: item.key,
|
||||
});
|
||||
|
||||
@@ -119,6 +119,12 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.statusMessageBadge {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.traceId {
|
||||
color: var(--accent-primary);
|
||||
overflow: hidden;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Badge } from '@signozhq/ui/badge';
|
||||
import ExpandableValue from 'periscope/components/ExpandableValue';
|
||||
import { SpanV3 } from 'types/api/trace/getTraceV3';
|
||||
|
||||
import styles from './SpanDetailsPanel.module.scss';
|
||||
@@ -48,7 +49,15 @@ export const HIGHLIGHTED_OPTIONS: HighlightedOption[] = [
|
||||
label: 'STATUS MESSAGE',
|
||||
render: (span): ReactNode | null =>
|
||||
span.status_message ? (
|
||||
<Badge color="vanilla">{span.status_message}</Badge>
|
||||
<ExpandableValue value={span.status_message} title="Status message">
|
||||
<Badge
|
||||
color="vanilla"
|
||||
textEllipsis="end"
|
||||
className={styles.statusMessageBadge}
|
||||
>
|
||||
{span.status_message}
|
||||
</Badge>
|
||||
</ExpandableValue>
|
||||
) : null,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.traceOptionsDropdown {
|
||||
z-index: 1100;
|
||||
}
|
||||
@@ -6,6 +6,8 @@ import { Ellipsis } from '@signozhq/icons';
|
||||
|
||||
import { useTraceStore } from '../stores/traceStore';
|
||||
|
||||
import styles from './TraceOptionsMenu.module.scss';
|
||||
|
||||
interface TraceOptionsMenuProps {
|
||||
showTraceDetails: boolean;
|
||||
onToggleTraceDetails: () => void;
|
||||
@@ -82,7 +84,11 @@ function TraceOptionsMenu({
|
||||
]);
|
||||
|
||||
return (
|
||||
<Dropdown menu={{ items: menuItems }} align="start">
|
||||
<Dropdown
|
||||
menu={{ items: menuItems }}
|
||||
align="start"
|
||||
className={styles.traceOptionsDropdown}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,13 +24,12 @@ HASTOKEN=23
|
||||
HAS=24
|
||||
HASANY=25
|
||||
HASALL=26
|
||||
SEARCH=27
|
||||
BOOL=28
|
||||
NUMBER=29
|
||||
QUOTED_TEXT=30
|
||||
KEY=31
|
||||
WS=32
|
||||
FREETEXT=33
|
||||
BOOL=27
|
||||
NUMBER=28
|
||||
QUOTED_TEXT=29
|
||||
KEY=30
|
||||
WS=31
|
||||
FREETEXT=32
|
||||
'('=1
|
||||
')'=2
|
||||
'['=3
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,13 +24,12 @@ HASTOKEN=23
|
||||
HAS=24
|
||||
HASANY=25
|
||||
HASALL=26
|
||||
SEARCH=27
|
||||
BOOL=28
|
||||
NUMBER=29
|
||||
QUOTED_TEXT=30
|
||||
KEY=31
|
||||
WS=32
|
||||
FREETEXT=33
|
||||
BOOL=27
|
||||
NUMBER=28
|
||||
QUOTED_TEXT=29
|
||||
KEY=30
|
||||
WS=31
|
||||
FREETEXT=32
|
||||
'('=1
|
||||
')'=2
|
||||
'['=3
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
// noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols
|
||||
import {
|
||||
ATN,
|
||||
@@ -38,13 +38,12 @@ export default class FilterQueryLexer extends Lexer {
|
||||
public static readonly HAS = 24;
|
||||
public static readonly HASANY = 25;
|
||||
public static readonly HASALL = 26;
|
||||
public static readonly SEARCH = 27;
|
||||
public static readonly BOOL = 28;
|
||||
public static readonly NUMBER = 29;
|
||||
public static readonly QUOTED_TEXT = 30;
|
||||
public static readonly KEY = 31;
|
||||
public static readonly WS = 32;
|
||||
public static readonly FREETEXT = 33;
|
||||
public static readonly BOOL = 27;
|
||||
public static readonly NUMBER = 28;
|
||||
public static readonly QUOTED_TEXT = 29;
|
||||
public static readonly KEY = 30;
|
||||
public static readonly WS = 31;
|
||||
public static readonly FREETEXT = 32;
|
||||
public static readonly EOF = Token.EOF;
|
||||
|
||||
public static readonly channelNames: string[] = [ "DEFAULT_TOKEN_CHANNEL", "HIDDEN" ];
|
||||
@@ -69,9 +68,8 @@ export default class FilterQueryLexer extends Lexer {
|
||||
"AND", "OR",
|
||||
"HASTOKEN",
|
||||
"HAS", "HASANY",
|
||||
"HASALL", "SEARCH",
|
||||
"BOOL", "NUMBER",
|
||||
"QUOTED_TEXT",
|
||||
"HASALL", "BOOL",
|
||||
"NUMBER", "QUOTED_TEXT",
|
||||
"KEY", "WS",
|
||||
"FREETEXT" ];
|
||||
public static readonly modeNames: string[] = [ "DEFAULT_MODE", ];
|
||||
@@ -80,8 +78,8 @@ export default class FilterQueryLexer extends Lexer {
|
||||
"LPAREN", "RPAREN", "LBRACK", "RBRACK", "COMMA", "EQUALS", "NOT_EQUALS",
|
||||
"NEQ", "LT", "LE", "GT", "GE", "LIKE", "ILIKE", "BETWEEN", "EXISTS", "REGEXP",
|
||||
"CONTAINS", "IN", "NOT", "AND", "OR", "HASTOKEN", "HAS", "HASANY", "HASALL",
|
||||
"SEARCH", "BOOL", "SIGN", "NUMBER", "QUOTED_TEXT", "SEGMENT", "EMPTY_BRACKS",
|
||||
"OLD_JSON_BRACKS", "KEY", "WS", "DIGIT", "FREETEXT",
|
||||
"BOOL", "SIGN", "NUMBER", "QUOTED_TEXT", "SEGMENT", "EMPTY_BRACKS", "OLD_JSON_BRACKS",
|
||||
"KEY", "WS", "DIGIT", "FREETEXT",
|
||||
];
|
||||
|
||||
|
||||
@@ -102,122 +100,119 @@ export default class FilterQueryLexer extends Lexer {
|
||||
|
||||
public get modeNames(): string[] { return FilterQueryLexer.modeNames; }
|
||||
|
||||
public static readonly _serializedATN: number[] = [4,0,33,329,6,-1,2,0,
|
||||
public static readonly _serializedATN: number[] = [4,0,32,320,6,-1,2,0,
|
||||
7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,
|
||||
7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,
|
||||
16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,
|
||||
2,24,7,24,2,25,7,25,2,26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,
|
||||
31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,1,0,
|
||||
1,0,1,1,1,1,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,3,5,91,8,5,1,6,1,6,1,6,
|
||||
1,7,1,7,1,7,1,8,1,8,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,
|
||||
1,12,1,12,1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,
|
||||
14,1,14,1,15,1,15,1,15,1,15,1,15,1,15,3,15,134,8,15,1,16,1,16,1,16,1,16,
|
||||
1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,3,17,151,8,17,1,
|
||||
18,1,18,1,18,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,22,
|
||||
1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,24,1,24,1,
|
||||
24,1,24,1,24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,
|
||||
1,26,1,26,1,26,1,26,1,27,1,27,1,27,1,27,1,27,1,27,1,27,1,27,1,27,3,27,210,
|
||||
8,27,1,28,1,28,1,29,3,29,215,8,29,1,29,4,29,218,8,29,11,29,12,29,219,1,
|
||||
29,1,29,5,29,224,8,29,10,29,12,29,227,9,29,3,29,229,8,29,1,29,1,29,3,29,
|
||||
233,8,29,1,29,4,29,236,8,29,11,29,12,29,237,3,29,240,8,29,1,29,3,29,243,
|
||||
8,29,1,29,1,29,4,29,247,8,29,11,29,12,29,248,1,29,1,29,3,29,253,8,29,1,
|
||||
29,4,29,256,8,29,11,29,12,29,257,3,29,260,8,29,3,29,262,8,29,1,30,1,30,
|
||||
1,30,1,30,5,30,268,8,30,10,30,12,30,271,9,30,1,30,1,30,1,30,1,30,1,30,5,
|
||||
30,278,8,30,10,30,12,30,281,9,30,1,30,3,30,284,8,30,1,31,1,31,5,31,288,
|
||||
8,31,10,31,12,31,291,9,31,1,32,1,32,1,32,1,33,1,33,1,33,1,33,1,34,1,34,
|
||||
1,34,1,34,1,34,1,34,1,34,4,34,307,8,34,11,34,12,34,308,5,34,311,8,34,10,
|
||||
34,12,34,314,9,34,1,35,4,35,317,8,35,11,35,12,35,318,1,35,1,35,1,36,1,36,
|
||||
1,37,4,37,326,8,37,11,37,12,37,327,0,0,38,1,1,3,2,5,3,7,4,9,5,11,6,13,7,
|
||||
15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,16,33,17,35,18,37,19,39,
|
||||
20,41,21,43,22,45,23,47,24,49,25,51,26,53,27,55,28,57,0,59,29,61,30,63,
|
||||
0,65,0,67,0,69,31,71,32,73,0,75,33,1,0,29,2,0,76,76,108,108,2,0,73,73,105,
|
||||
105,2,0,75,75,107,107,2,0,69,69,101,101,2,0,66,66,98,98,2,0,84,84,116,116,
|
||||
2,0,87,87,119,119,2,0,78,78,110,110,2,0,88,88,120,120,2,0,83,83,115,115,
|
||||
2,0,82,82,114,114,2,0,71,71,103,103,2,0,80,80,112,112,2,0,67,67,99,99,2,
|
||||
0,79,79,111,111,2,0,65,65,97,97,2,0,68,68,100,100,2,0,72,72,104,104,2,0,
|
||||
89,89,121,121,2,0,85,85,117,117,2,0,70,70,102,102,2,0,43,43,45,45,2,0,34,
|
||||
34,92,92,2,0,39,39,92,92,4,0,35,36,64,90,95,95,97,123,7,0,35,36,45,45,47,
|
||||
58,64,90,95,95,97,123,125,125,3,0,9,10,13,13,32,32,1,0,48,57,8,0,9,10,13,
|
||||
13,32,34,39,41,44,44,60,62,91,91,93,93,353,0,1,1,0,0,0,0,3,1,0,0,0,0,5,
|
||||
1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,
|
||||
0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,
|
||||
0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,
|
||||
0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,1,
|
||||
0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,59,1,0,0,0,0,61,1,0,0,0,
|
||||
0,69,1,0,0,0,0,71,1,0,0,0,0,75,1,0,0,0,1,77,1,0,0,0,3,79,1,0,0,0,5,81,1,
|
||||
0,0,0,7,83,1,0,0,0,9,85,1,0,0,0,11,90,1,0,0,0,13,92,1,0,0,0,15,95,1,0,0,
|
||||
0,17,98,1,0,0,0,19,100,1,0,0,0,21,103,1,0,0,0,23,105,1,0,0,0,25,108,1,0,
|
||||
0,0,27,113,1,0,0,0,29,119,1,0,0,0,31,127,1,0,0,0,33,135,1,0,0,0,35,142,
|
||||
1,0,0,0,37,152,1,0,0,0,39,155,1,0,0,0,41,159,1,0,0,0,43,163,1,0,0,0,45,
|
||||
166,1,0,0,0,47,175,1,0,0,0,49,179,1,0,0,0,51,186,1,0,0,0,53,193,1,0,0,0,
|
||||
55,209,1,0,0,0,57,211,1,0,0,0,59,261,1,0,0,0,61,283,1,0,0,0,63,285,1,0,
|
||||
0,0,65,292,1,0,0,0,67,295,1,0,0,0,69,299,1,0,0,0,71,316,1,0,0,0,73,322,
|
||||
1,0,0,0,75,325,1,0,0,0,77,78,5,40,0,0,78,2,1,0,0,0,79,80,5,41,0,0,80,4,
|
||||
1,0,0,0,81,82,5,91,0,0,82,6,1,0,0,0,83,84,5,93,0,0,84,8,1,0,0,0,85,86,5,
|
||||
44,0,0,86,10,1,0,0,0,87,91,5,61,0,0,88,89,5,61,0,0,89,91,5,61,0,0,90,87,
|
||||
1,0,0,0,90,88,1,0,0,0,91,12,1,0,0,0,92,93,5,33,0,0,93,94,5,61,0,0,94,14,
|
||||
1,0,0,0,95,96,5,60,0,0,96,97,5,62,0,0,97,16,1,0,0,0,98,99,5,60,0,0,99,18,
|
||||
1,0,0,0,100,101,5,60,0,0,101,102,5,61,0,0,102,20,1,0,0,0,103,104,5,62,0,
|
||||
0,104,22,1,0,0,0,105,106,5,62,0,0,106,107,5,61,0,0,107,24,1,0,0,0,108,109,
|
||||
7,0,0,0,109,110,7,1,0,0,110,111,7,2,0,0,111,112,7,3,0,0,112,26,1,0,0,0,
|
||||
113,114,7,1,0,0,114,115,7,0,0,0,115,116,7,1,0,0,116,117,7,2,0,0,117,118,
|
||||
7,3,0,0,118,28,1,0,0,0,119,120,7,4,0,0,120,121,7,3,0,0,121,122,7,5,0,0,
|
||||
122,123,7,6,0,0,123,124,7,3,0,0,124,125,7,3,0,0,125,126,7,7,0,0,126,30,
|
||||
1,0,0,0,127,128,7,3,0,0,128,129,7,8,0,0,129,130,7,1,0,0,130,131,7,9,0,0,
|
||||
131,133,7,5,0,0,132,134,7,9,0,0,133,132,1,0,0,0,133,134,1,0,0,0,134,32,
|
||||
1,0,0,0,135,136,7,10,0,0,136,137,7,3,0,0,137,138,7,11,0,0,138,139,7,3,0,
|
||||
0,139,140,7,8,0,0,140,141,7,12,0,0,141,34,1,0,0,0,142,143,7,13,0,0,143,
|
||||
144,7,14,0,0,144,145,7,7,0,0,145,146,7,5,0,0,146,147,7,15,0,0,147,148,7,
|
||||
1,0,0,148,150,7,7,0,0,149,151,7,9,0,0,150,149,1,0,0,0,150,151,1,0,0,0,151,
|
||||
36,1,0,0,0,152,153,7,1,0,0,153,154,7,7,0,0,154,38,1,0,0,0,155,156,7,7,0,
|
||||
0,156,157,7,14,0,0,157,158,7,5,0,0,158,40,1,0,0,0,159,160,7,15,0,0,160,
|
||||
161,7,7,0,0,161,162,7,16,0,0,162,42,1,0,0,0,163,164,7,14,0,0,164,165,7,
|
||||
10,0,0,165,44,1,0,0,0,166,167,7,17,0,0,167,168,7,15,0,0,168,169,7,9,0,0,
|
||||
169,170,7,5,0,0,170,171,7,14,0,0,171,172,7,2,0,0,172,173,7,3,0,0,173,174,
|
||||
7,7,0,0,174,46,1,0,0,0,175,176,7,17,0,0,176,177,7,15,0,0,177,178,7,9,0,
|
||||
0,178,48,1,0,0,0,179,180,7,17,0,0,180,181,7,15,0,0,181,182,7,9,0,0,182,
|
||||
183,7,15,0,0,183,184,7,7,0,0,184,185,7,18,0,0,185,50,1,0,0,0,186,187,7,
|
||||
17,0,0,187,188,7,15,0,0,188,189,7,9,0,0,189,190,7,15,0,0,190,191,7,0,0,
|
||||
0,191,192,7,0,0,0,192,52,1,0,0,0,193,194,7,9,0,0,194,195,7,3,0,0,195,196,
|
||||
7,15,0,0,196,197,7,10,0,0,197,198,7,13,0,0,198,199,7,17,0,0,199,54,1,0,
|
||||
0,0,200,201,7,5,0,0,201,202,7,10,0,0,202,203,7,19,0,0,203,210,7,3,0,0,204,
|
||||
205,7,20,0,0,205,206,7,15,0,0,206,207,7,0,0,0,207,208,7,9,0,0,208,210,7,
|
||||
3,0,0,209,200,1,0,0,0,209,204,1,0,0,0,210,56,1,0,0,0,211,212,7,21,0,0,212,
|
||||
58,1,0,0,0,213,215,3,57,28,0,214,213,1,0,0,0,214,215,1,0,0,0,215,217,1,
|
||||
0,0,0,216,218,3,73,36,0,217,216,1,0,0,0,218,219,1,0,0,0,219,217,1,0,0,0,
|
||||
219,220,1,0,0,0,220,228,1,0,0,0,221,225,5,46,0,0,222,224,3,73,36,0,223,
|
||||
222,1,0,0,0,224,227,1,0,0,0,225,223,1,0,0,0,225,226,1,0,0,0,226,229,1,0,
|
||||
0,0,227,225,1,0,0,0,228,221,1,0,0,0,228,229,1,0,0,0,229,239,1,0,0,0,230,
|
||||
232,7,3,0,0,231,233,3,57,28,0,232,231,1,0,0,0,232,233,1,0,0,0,233,235,1,
|
||||
0,0,0,234,236,3,73,36,0,235,234,1,0,0,0,236,237,1,0,0,0,237,235,1,0,0,0,
|
||||
237,238,1,0,0,0,238,240,1,0,0,0,239,230,1,0,0,0,239,240,1,0,0,0,240,262,
|
||||
1,0,0,0,241,243,3,57,28,0,242,241,1,0,0,0,242,243,1,0,0,0,243,244,1,0,0,
|
||||
0,244,246,5,46,0,0,245,247,3,73,36,0,246,245,1,0,0,0,247,248,1,0,0,0,248,
|
||||
246,1,0,0,0,248,249,1,0,0,0,249,259,1,0,0,0,250,252,7,3,0,0,251,253,3,57,
|
||||
28,0,252,251,1,0,0,0,252,253,1,0,0,0,253,255,1,0,0,0,254,256,3,73,36,0,
|
||||
255,254,1,0,0,0,256,257,1,0,0,0,257,255,1,0,0,0,257,258,1,0,0,0,258,260,
|
||||
1,0,0,0,259,250,1,0,0,0,259,260,1,0,0,0,260,262,1,0,0,0,261,214,1,0,0,0,
|
||||
261,242,1,0,0,0,262,60,1,0,0,0,263,269,5,34,0,0,264,268,8,22,0,0,265,266,
|
||||
5,92,0,0,266,268,9,0,0,0,267,264,1,0,0,0,267,265,1,0,0,0,268,271,1,0,0,
|
||||
0,269,267,1,0,0,0,269,270,1,0,0,0,270,272,1,0,0,0,271,269,1,0,0,0,272,284,
|
||||
5,34,0,0,273,279,5,39,0,0,274,278,8,23,0,0,275,276,5,92,0,0,276,278,9,0,
|
||||
0,0,277,274,1,0,0,0,277,275,1,0,0,0,278,281,1,0,0,0,279,277,1,0,0,0,279,
|
||||
280,1,0,0,0,280,282,1,0,0,0,281,279,1,0,0,0,282,284,5,39,0,0,283,263,1,
|
||||
0,0,0,283,273,1,0,0,0,284,62,1,0,0,0,285,289,7,24,0,0,286,288,7,25,0,0,
|
||||
287,286,1,0,0,0,288,291,1,0,0,0,289,287,1,0,0,0,289,290,1,0,0,0,290,64,
|
||||
1,0,0,0,291,289,1,0,0,0,292,293,5,91,0,0,293,294,5,93,0,0,294,66,1,0,0,
|
||||
0,295,296,5,91,0,0,296,297,5,42,0,0,297,298,5,93,0,0,298,68,1,0,0,0,299,
|
||||
312,3,63,31,0,300,301,5,46,0,0,301,311,3,63,31,0,302,311,3,65,32,0,303,
|
||||
311,3,67,33,0,304,306,5,46,0,0,305,307,3,73,36,0,306,305,1,0,0,0,307,308,
|
||||
1,0,0,0,308,306,1,0,0,0,308,309,1,0,0,0,309,311,1,0,0,0,310,300,1,0,0,0,
|
||||
310,302,1,0,0,0,310,303,1,0,0,0,310,304,1,0,0,0,311,314,1,0,0,0,312,310,
|
||||
1,0,0,0,312,313,1,0,0,0,313,70,1,0,0,0,314,312,1,0,0,0,315,317,7,26,0,0,
|
||||
316,315,1,0,0,0,317,318,1,0,0,0,318,316,1,0,0,0,318,319,1,0,0,0,319,320,
|
||||
1,0,0,0,320,321,6,35,0,0,321,72,1,0,0,0,322,323,7,27,0,0,323,74,1,0,0,0,
|
||||
324,326,8,28,0,0,325,324,1,0,0,0,326,327,1,0,0,0,327,325,1,0,0,0,327,328,
|
||||
1,0,0,0,328,76,1,0,0,0,29,0,90,133,150,209,214,219,225,228,232,237,239,
|
||||
242,248,252,257,259,261,267,269,277,279,283,289,308,310,312,318,327,1,6,
|
||||
0,0];
|
||||
31,7,31,2,32,7,32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,1,0,1,0,1,1,1,
|
||||
1,1,2,1,2,1,3,1,3,1,4,1,4,1,5,1,5,1,5,3,5,89,8,5,1,6,1,6,1,6,1,7,1,7,1,
|
||||
7,1,8,1,8,1,9,1,9,1,9,1,10,1,10,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,
|
||||
1,13,1,13,1,13,1,13,1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,14,1,
|
||||
15,1,15,1,15,1,15,1,15,1,15,3,15,132,8,15,1,16,1,16,1,16,1,16,1,16,1,16,
|
||||
1,16,1,17,1,17,1,17,1,17,1,17,1,17,1,17,1,17,3,17,149,8,17,1,18,1,18,1,
|
||||
18,1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,22,1,22,1,22,
|
||||
1,22,1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,24,1,24,1,24,1,24,1,
|
||||
24,1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,1,26,1,26,
|
||||
1,26,1,26,1,26,1,26,3,26,201,8,26,1,27,1,27,1,28,3,28,206,8,28,1,28,4,28,
|
||||
209,8,28,11,28,12,28,210,1,28,1,28,5,28,215,8,28,10,28,12,28,218,9,28,3,
|
||||
28,220,8,28,1,28,1,28,3,28,224,8,28,1,28,4,28,227,8,28,11,28,12,28,228,
|
||||
3,28,231,8,28,1,28,3,28,234,8,28,1,28,1,28,4,28,238,8,28,11,28,12,28,239,
|
||||
1,28,1,28,3,28,244,8,28,1,28,4,28,247,8,28,11,28,12,28,248,3,28,251,8,28,
|
||||
3,28,253,8,28,1,29,1,29,1,29,1,29,5,29,259,8,29,10,29,12,29,262,9,29,1,
|
||||
29,1,29,1,29,1,29,1,29,5,29,269,8,29,10,29,12,29,272,9,29,1,29,3,29,275,
|
||||
8,29,1,30,1,30,5,30,279,8,30,10,30,12,30,282,9,30,1,31,1,31,1,31,1,32,1,
|
||||
32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,33,1,33,4,33,298,8,33,11,33,12,
|
||||
33,299,5,33,302,8,33,10,33,12,33,305,9,33,1,34,4,34,308,8,34,11,34,12,34,
|
||||
309,1,34,1,34,1,35,1,35,1,36,4,36,317,8,36,11,36,12,36,318,0,0,37,1,1,3,
|
||||
2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,
|
||||
16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,51,26,53,27,55,
|
||||
0,57,28,59,29,61,0,63,0,65,0,67,30,69,31,71,0,73,32,1,0,29,2,0,76,76,108,
|
||||
108,2,0,73,73,105,105,2,0,75,75,107,107,2,0,69,69,101,101,2,0,66,66,98,
|
||||
98,2,0,84,84,116,116,2,0,87,87,119,119,2,0,78,78,110,110,2,0,88,88,120,
|
||||
120,2,0,83,83,115,115,2,0,82,82,114,114,2,0,71,71,103,103,2,0,80,80,112,
|
||||
112,2,0,67,67,99,99,2,0,79,79,111,111,2,0,65,65,97,97,2,0,68,68,100,100,
|
||||
2,0,72,72,104,104,2,0,89,89,121,121,2,0,85,85,117,117,2,0,70,70,102,102,
|
||||
2,0,43,43,45,45,2,0,34,34,92,92,2,0,39,39,92,92,4,0,35,36,64,90,95,95,97,
|
||||
123,7,0,35,36,45,45,47,58,64,90,95,95,97,123,125,125,3,0,9,10,13,13,32,
|
||||
32,1,0,48,57,8,0,9,10,13,13,32,34,39,41,44,44,60,62,91,91,93,93,344,0,1,
|
||||
1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1,0,0,0,0,11,1,0,0,0,0,
|
||||
13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0,0,21,1,0,0,0,0,23,1,0,
|
||||
0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,
|
||||
35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,
|
||||
0,0,0,47,1,0,0,0,0,49,1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,57,1,0,0,0,0,
|
||||
59,1,0,0,0,0,67,1,0,0,0,0,69,1,0,0,0,0,73,1,0,0,0,1,75,1,0,0,0,3,77,1,0,
|
||||
0,0,5,79,1,0,0,0,7,81,1,0,0,0,9,83,1,0,0,0,11,88,1,0,0,0,13,90,1,0,0,0,
|
||||
15,93,1,0,0,0,17,96,1,0,0,0,19,98,1,0,0,0,21,101,1,0,0,0,23,103,1,0,0,0,
|
||||
25,106,1,0,0,0,27,111,1,0,0,0,29,117,1,0,0,0,31,125,1,0,0,0,33,133,1,0,
|
||||
0,0,35,140,1,0,0,0,37,150,1,0,0,0,39,153,1,0,0,0,41,157,1,0,0,0,43,161,
|
||||
1,0,0,0,45,164,1,0,0,0,47,173,1,0,0,0,49,177,1,0,0,0,51,184,1,0,0,0,53,
|
||||
200,1,0,0,0,55,202,1,0,0,0,57,252,1,0,0,0,59,274,1,0,0,0,61,276,1,0,0,0,
|
||||
63,283,1,0,0,0,65,286,1,0,0,0,67,290,1,0,0,0,69,307,1,0,0,0,71,313,1,0,
|
||||
0,0,73,316,1,0,0,0,75,76,5,40,0,0,76,2,1,0,0,0,77,78,5,41,0,0,78,4,1,0,
|
||||
0,0,79,80,5,91,0,0,80,6,1,0,0,0,81,82,5,93,0,0,82,8,1,0,0,0,83,84,5,44,
|
||||
0,0,84,10,1,0,0,0,85,89,5,61,0,0,86,87,5,61,0,0,87,89,5,61,0,0,88,85,1,
|
||||
0,0,0,88,86,1,0,0,0,89,12,1,0,0,0,90,91,5,33,0,0,91,92,5,61,0,0,92,14,1,
|
||||
0,0,0,93,94,5,60,0,0,94,95,5,62,0,0,95,16,1,0,0,0,96,97,5,60,0,0,97,18,
|
||||
1,0,0,0,98,99,5,60,0,0,99,100,5,61,0,0,100,20,1,0,0,0,101,102,5,62,0,0,
|
||||
102,22,1,0,0,0,103,104,5,62,0,0,104,105,5,61,0,0,105,24,1,0,0,0,106,107,
|
||||
7,0,0,0,107,108,7,1,0,0,108,109,7,2,0,0,109,110,7,3,0,0,110,26,1,0,0,0,
|
||||
111,112,7,1,0,0,112,113,7,0,0,0,113,114,7,1,0,0,114,115,7,2,0,0,115,116,
|
||||
7,3,0,0,116,28,1,0,0,0,117,118,7,4,0,0,118,119,7,3,0,0,119,120,7,5,0,0,
|
||||
120,121,7,6,0,0,121,122,7,3,0,0,122,123,7,3,0,0,123,124,7,7,0,0,124,30,
|
||||
1,0,0,0,125,126,7,3,0,0,126,127,7,8,0,0,127,128,7,1,0,0,128,129,7,9,0,0,
|
||||
129,131,7,5,0,0,130,132,7,9,0,0,131,130,1,0,0,0,131,132,1,0,0,0,132,32,
|
||||
1,0,0,0,133,134,7,10,0,0,134,135,7,3,0,0,135,136,7,11,0,0,136,137,7,3,0,
|
||||
0,137,138,7,8,0,0,138,139,7,12,0,0,139,34,1,0,0,0,140,141,7,13,0,0,141,
|
||||
142,7,14,0,0,142,143,7,7,0,0,143,144,7,5,0,0,144,145,7,15,0,0,145,146,7,
|
||||
1,0,0,146,148,7,7,0,0,147,149,7,9,0,0,148,147,1,0,0,0,148,149,1,0,0,0,149,
|
||||
36,1,0,0,0,150,151,7,1,0,0,151,152,7,7,0,0,152,38,1,0,0,0,153,154,7,7,0,
|
||||
0,154,155,7,14,0,0,155,156,7,5,0,0,156,40,1,0,0,0,157,158,7,15,0,0,158,
|
||||
159,7,7,0,0,159,160,7,16,0,0,160,42,1,0,0,0,161,162,7,14,0,0,162,163,7,
|
||||
10,0,0,163,44,1,0,0,0,164,165,7,17,0,0,165,166,7,15,0,0,166,167,7,9,0,0,
|
||||
167,168,7,5,0,0,168,169,7,14,0,0,169,170,7,2,0,0,170,171,7,3,0,0,171,172,
|
||||
7,7,0,0,172,46,1,0,0,0,173,174,7,17,0,0,174,175,7,15,0,0,175,176,7,9,0,
|
||||
0,176,48,1,0,0,0,177,178,7,17,0,0,178,179,7,15,0,0,179,180,7,9,0,0,180,
|
||||
181,7,15,0,0,181,182,7,7,0,0,182,183,7,18,0,0,183,50,1,0,0,0,184,185,7,
|
||||
17,0,0,185,186,7,15,0,0,186,187,7,9,0,0,187,188,7,15,0,0,188,189,7,0,0,
|
||||
0,189,190,7,0,0,0,190,52,1,0,0,0,191,192,7,5,0,0,192,193,7,10,0,0,193,194,
|
||||
7,19,0,0,194,201,7,3,0,0,195,196,7,20,0,0,196,197,7,15,0,0,197,198,7,0,
|
||||
0,0,198,199,7,9,0,0,199,201,7,3,0,0,200,191,1,0,0,0,200,195,1,0,0,0,201,
|
||||
54,1,0,0,0,202,203,7,21,0,0,203,56,1,0,0,0,204,206,3,55,27,0,205,204,1,
|
||||
0,0,0,205,206,1,0,0,0,206,208,1,0,0,0,207,209,3,71,35,0,208,207,1,0,0,0,
|
||||
209,210,1,0,0,0,210,208,1,0,0,0,210,211,1,0,0,0,211,219,1,0,0,0,212,216,
|
||||
5,46,0,0,213,215,3,71,35,0,214,213,1,0,0,0,215,218,1,0,0,0,216,214,1,0,
|
||||
0,0,216,217,1,0,0,0,217,220,1,0,0,0,218,216,1,0,0,0,219,212,1,0,0,0,219,
|
||||
220,1,0,0,0,220,230,1,0,0,0,221,223,7,3,0,0,222,224,3,55,27,0,223,222,1,
|
||||
0,0,0,223,224,1,0,0,0,224,226,1,0,0,0,225,227,3,71,35,0,226,225,1,0,0,0,
|
||||
227,228,1,0,0,0,228,226,1,0,0,0,228,229,1,0,0,0,229,231,1,0,0,0,230,221,
|
||||
1,0,0,0,230,231,1,0,0,0,231,253,1,0,0,0,232,234,3,55,27,0,233,232,1,0,0,
|
||||
0,233,234,1,0,0,0,234,235,1,0,0,0,235,237,5,46,0,0,236,238,3,71,35,0,237,
|
||||
236,1,0,0,0,238,239,1,0,0,0,239,237,1,0,0,0,239,240,1,0,0,0,240,250,1,0,
|
||||
0,0,241,243,7,3,0,0,242,244,3,55,27,0,243,242,1,0,0,0,243,244,1,0,0,0,244,
|
||||
246,1,0,0,0,245,247,3,71,35,0,246,245,1,0,0,0,247,248,1,0,0,0,248,246,1,
|
||||
0,0,0,248,249,1,0,0,0,249,251,1,0,0,0,250,241,1,0,0,0,250,251,1,0,0,0,251,
|
||||
253,1,0,0,0,252,205,1,0,0,0,252,233,1,0,0,0,253,58,1,0,0,0,254,260,5,34,
|
||||
0,0,255,259,8,22,0,0,256,257,5,92,0,0,257,259,9,0,0,0,258,255,1,0,0,0,258,
|
||||
256,1,0,0,0,259,262,1,0,0,0,260,258,1,0,0,0,260,261,1,0,0,0,261,263,1,0,
|
||||
0,0,262,260,1,0,0,0,263,275,5,34,0,0,264,270,5,39,0,0,265,269,8,23,0,0,
|
||||
266,267,5,92,0,0,267,269,9,0,0,0,268,265,1,0,0,0,268,266,1,0,0,0,269,272,
|
||||
1,0,0,0,270,268,1,0,0,0,270,271,1,0,0,0,271,273,1,0,0,0,272,270,1,0,0,0,
|
||||
273,275,5,39,0,0,274,254,1,0,0,0,274,264,1,0,0,0,275,60,1,0,0,0,276,280,
|
||||
7,24,0,0,277,279,7,25,0,0,278,277,1,0,0,0,279,282,1,0,0,0,280,278,1,0,0,
|
||||
0,280,281,1,0,0,0,281,62,1,0,0,0,282,280,1,0,0,0,283,284,5,91,0,0,284,285,
|
||||
5,93,0,0,285,64,1,0,0,0,286,287,5,91,0,0,287,288,5,42,0,0,288,289,5,93,
|
||||
0,0,289,66,1,0,0,0,290,303,3,61,30,0,291,292,5,46,0,0,292,302,3,61,30,0,
|
||||
293,302,3,63,31,0,294,302,3,65,32,0,295,297,5,46,0,0,296,298,3,71,35,0,
|
||||
297,296,1,0,0,0,298,299,1,0,0,0,299,297,1,0,0,0,299,300,1,0,0,0,300,302,
|
||||
1,0,0,0,301,291,1,0,0,0,301,293,1,0,0,0,301,294,1,0,0,0,301,295,1,0,0,0,
|
||||
302,305,1,0,0,0,303,301,1,0,0,0,303,304,1,0,0,0,304,68,1,0,0,0,305,303,
|
||||
1,0,0,0,306,308,7,26,0,0,307,306,1,0,0,0,308,309,1,0,0,0,309,307,1,0,0,
|
||||
0,309,310,1,0,0,0,310,311,1,0,0,0,311,312,6,34,0,0,312,70,1,0,0,0,313,314,
|
||||
7,27,0,0,314,72,1,0,0,0,315,317,8,28,0,0,316,315,1,0,0,0,317,318,1,0,0,
|
||||
0,318,316,1,0,0,0,318,319,1,0,0,0,319,74,1,0,0,0,29,0,88,131,148,200,205,
|
||||
210,216,219,223,228,230,233,239,243,248,250,252,258,260,268,270,274,280,
|
||||
299,301,303,309,318,1,6,0,0];
|
||||
|
||||
private static __ATN: ATN;
|
||||
public static get _ATN(): ATN {
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
|
||||
import {ParseTreeListener} from "antlr4";
|
||||
|
||||
|
||||
import { QueryContext } from "./FilterQueryParser.js";
|
||||
import { ExpressionContext } from "./FilterQueryParser.js";
|
||||
import { OrExpressionContext } from "./FilterQueryParser.js";
|
||||
import { AndExpressionContext } from "./FilterQueryParser.js";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser.js";
|
||||
import { PrimaryContext } from "./FilterQueryParser.js";
|
||||
import { ComparisonContext } from "./FilterQueryParser.js";
|
||||
import { InClauseContext } from "./FilterQueryParser.js";
|
||||
import { NotInClauseContext } from "./FilterQueryParser.js";
|
||||
import { ValueListContext } from "./FilterQueryParser.js";
|
||||
import { FullTextContext } from "./FilterQueryParser.js";
|
||||
import { FunctionCallContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamContext } from "./FilterQueryParser.js";
|
||||
import { ArrayContext } from "./FilterQueryParser.js";
|
||||
import { ValueContext } from "./FilterQueryParser.js";
|
||||
import { KeyContext } from "./FilterQueryParser.js";
|
||||
import { QueryContext } from "./FilterQueryParser";
|
||||
import { ExpressionContext } from "./FilterQueryParser";
|
||||
import { OrExpressionContext } from "./FilterQueryParser";
|
||||
import { AndExpressionContext } from "./FilterQueryParser";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser";
|
||||
import { PrimaryContext } from "./FilterQueryParser";
|
||||
import { ComparisonContext } from "./FilterQueryParser";
|
||||
import { InClauseContext } from "./FilterQueryParser";
|
||||
import { NotInClauseContext } from "./FilterQueryParser";
|
||||
import { ValueListContext } from "./FilterQueryParser";
|
||||
import { FullTextContext } from "./FilterQueryParser";
|
||||
import { FunctionCallContext } from "./FilterQueryParser";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser";
|
||||
import { FunctionParamContext } from "./FilterQueryParser";
|
||||
import { ArrayContext } from "./FilterQueryParser";
|
||||
import { ValueContext } from "./FilterQueryParser";
|
||||
import { KeyContext } from "./FilterQueryParser";
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
// noinspection ES6UnusedImports,JSUnusedGlobalSymbols,JSUnusedLocalSymbols
|
||||
|
||||
import {
|
||||
@@ -45,14 +45,13 @@ export default class FilterQueryParser extends Parser {
|
||||
public static readonly HAS = 24;
|
||||
public static readonly HASANY = 25;
|
||||
public static readonly HASALL = 26;
|
||||
public static readonly SEARCH = 27;
|
||||
public static readonly BOOL = 28;
|
||||
public static readonly NUMBER = 29;
|
||||
public static readonly QUOTED_TEXT = 30;
|
||||
public static readonly KEY = 31;
|
||||
public static readonly WS = 32;
|
||||
public static readonly FREETEXT = 33;
|
||||
public static override readonly EOF = Token.EOF;
|
||||
public static readonly BOOL = 27;
|
||||
public static readonly NUMBER = 28;
|
||||
public static readonly QUOTED_TEXT = 29;
|
||||
public static readonly KEY = 30;
|
||||
public static readonly WS = 31;
|
||||
public static readonly FREETEXT = 32;
|
||||
public static readonly EOF = Token.EOF;
|
||||
public static readonly RULE_query = 0;
|
||||
public static readonly RULE_expression = 1;
|
||||
public static readonly RULE_orExpression = 2;
|
||||
@@ -91,9 +90,8 @@ export default class FilterQueryParser extends Parser {
|
||||
"AND", "OR",
|
||||
"HASTOKEN",
|
||||
"HAS", "HASANY",
|
||||
"HASALL", "SEARCH",
|
||||
"BOOL", "NUMBER",
|
||||
"QUOTED_TEXT",
|
||||
"HASALL", "BOOL",
|
||||
"NUMBER", "QUOTED_TEXT",
|
||||
"KEY", "WS",
|
||||
"FREETEXT" ];
|
||||
// tslint:disable:no-trailing-whitespace
|
||||
@@ -224,7 +222,7 @@ export default class FilterQueryParser extends Parser {
|
||||
this.state = 53;
|
||||
this._errHandler.sync(this);
|
||||
_la = this._input.LA(1);
|
||||
while ((((_la) & ~0x1F) === 0 && ((1 << _la) & 4289724418) !== 0) || _la===33) {
|
||||
while (((((_la - 1)) & ~0x1F) === 0 && ((1 << (_la - 1)) & 3218604033) !== 0)) {
|
||||
{
|
||||
this.state = 51;
|
||||
this._errHandler.sync(this);
|
||||
@@ -247,8 +245,7 @@ export default class FilterQueryParser extends Parser {
|
||||
case 28:
|
||||
case 29:
|
||||
case 30:
|
||||
case 31:
|
||||
case 33:
|
||||
case 32:
|
||||
{
|
||||
this.state = 50;
|
||||
this.unaryExpression();
|
||||
@@ -814,7 +811,7 @@ export default class FilterQueryParser extends Parser {
|
||||
{
|
||||
this.state = 190;
|
||||
_la = this._input.LA(1);
|
||||
if(!(_la===30 || _la===33)) {
|
||||
if(!(_la===29 || _la===32)) {
|
||||
this._errHandler.recoverInline(this);
|
||||
}
|
||||
else {
|
||||
@@ -847,7 +844,7 @@ export default class FilterQueryParser extends Parser {
|
||||
{
|
||||
this.state = 192;
|
||||
_la = this._input.LA(1);
|
||||
if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 260046848) !== 0))) {
|
||||
if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 125829120) !== 0))) {
|
||||
this._errHandler.recoverInline(this);
|
||||
}
|
||||
else {
|
||||
@@ -1002,7 +999,7 @@ export default class FilterQueryParser extends Parser {
|
||||
{
|
||||
this.state = 214;
|
||||
_la = this._input.LA(1);
|
||||
if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 4026531840) !== 0))) {
|
||||
if(!((((_la) & ~0x1F) === 0 && ((1 << _la) & 2013265920) !== 0))) {
|
||||
this._errHandler.recoverInline(this);
|
||||
}
|
||||
else {
|
||||
@@ -1051,7 +1048,7 @@ export default class FilterQueryParser extends Parser {
|
||||
return localctx;
|
||||
}
|
||||
|
||||
public static readonly _serializedATN: number[] = [4,1,33,219,2,0,7,0,2,
|
||||
public static readonly _serializedATN: number[] = [4,1,32,219,2,0,7,0,2,
|
||||
1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,
|
||||
10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,1,0,
|
||||
1,0,1,0,1,1,1,1,1,2,1,2,1,2,5,2,43,8,2,10,2,12,2,46,9,2,1,3,1,3,1,3,1,3,
|
||||
@@ -1066,7 +1063,7 @@ export default class FilterQueryParser extends Parser {
|
||||
10,9,12,9,189,9,9,1,10,1,10,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,5,12,
|
||||
201,8,12,10,12,12,12,204,9,12,1,13,1,13,1,13,3,13,209,8,13,1,14,1,14,1,
|
||||
14,1,14,1,15,1,15,1,16,1,16,1,16,0,0,17,0,2,4,6,8,10,12,14,16,18,20,22,
|
||||
24,26,28,30,32,0,5,1,0,7,8,1,0,13,14,2,0,30,30,33,33,1,0,23,27,1,0,28,31,
|
||||
24,26,28,30,32,0,5,1,0,7,8,1,0,13,14,2,0,29,29,32,32,1,0,23,26,1,0,27,30,
|
||||
235,0,34,1,0,0,0,2,37,1,0,0,0,4,39,1,0,0,0,6,47,1,0,0,0,8,57,1,0,0,0,10,
|
||||
70,1,0,0,0,12,149,1,0,0,0,14,163,1,0,0,0,16,180,1,0,0,0,18,182,1,0,0,0,
|
||||
20,190,1,0,0,0,22,192,1,0,0,0,24,197,1,0,0,0,26,208,1,0,0,0,28,210,1,0,
|
||||
@@ -1118,7 +1115,7 @@ export default class FilterQueryParser extends Parser {
|
||||
0,0,205,209,3,32,16,0,206,209,3,30,15,0,207,209,3,28,14,0,208,205,1,0,0,
|
||||
0,208,206,1,0,0,0,208,207,1,0,0,0,209,27,1,0,0,0,210,211,5,3,0,0,211,212,
|
||||
3,18,9,0,212,213,5,4,0,0,213,29,1,0,0,0,214,215,7,4,0,0,215,31,1,0,0,0,
|
||||
216,217,5,31,0,0,217,33,1,0,0,0,11,44,51,53,57,70,149,163,180,187,202,208];
|
||||
216,217,5,30,0,0,217,33,1,0,0,0,11,44,51,53,57,70,149,163,180,187,202,208];
|
||||
|
||||
private static __ATN: ATN;
|
||||
public static get _ATN(): ATN {
|
||||
@@ -1665,9 +1662,6 @@ export class FunctionCallContext extends ParserRuleContext {
|
||||
public HASALL(): TerminalNode {
|
||||
return this.getToken(FilterQueryParser.HASALL, 0);
|
||||
}
|
||||
public SEARCH(): TerminalNode {
|
||||
return this.getToken(FilterQueryParser.SEARCH, 0);
|
||||
}
|
||||
public get ruleIndex(): number {
|
||||
return FilterQueryParser.RULE_functionCall;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Generated from grammar/FilterQuery.g4 by ANTLR 4.13.2
|
||||
// Generated from FilterQuery.g4 by ANTLR 4.13.1
|
||||
|
||||
import {ParseTreeVisitor} from 'antlr4';
|
||||
|
||||
|
||||
import { QueryContext } from "./FilterQueryParser.js";
|
||||
import { ExpressionContext } from "./FilterQueryParser.js";
|
||||
import { OrExpressionContext } from "./FilterQueryParser.js";
|
||||
import { AndExpressionContext } from "./FilterQueryParser.js";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser.js";
|
||||
import { PrimaryContext } from "./FilterQueryParser.js";
|
||||
import { ComparisonContext } from "./FilterQueryParser.js";
|
||||
import { InClauseContext } from "./FilterQueryParser.js";
|
||||
import { NotInClauseContext } from "./FilterQueryParser.js";
|
||||
import { ValueListContext } from "./FilterQueryParser.js";
|
||||
import { FullTextContext } from "./FilterQueryParser.js";
|
||||
import { FunctionCallContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser.js";
|
||||
import { FunctionParamContext } from "./FilterQueryParser.js";
|
||||
import { ArrayContext } from "./FilterQueryParser.js";
|
||||
import { ValueContext } from "./FilterQueryParser.js";
|
||||
import { KeyContext } from "./FilterQueryParser.js";
|
||||
import { QueryContext } from "./FilterQueryParser";
|
||||
import { ExpressionContext } from "./FilterQueryParser";
|
||||
import { OrExpressionContext } from "./FilterQueryParser";
|
||||
import { AndExpressionContext } from "./FilterQueryParser";
|
||||
import { UnaryExpressionContext } from "./FilterQueryParser";
|
||||
import { PrimaryContext } from "./FilterQueryParser";
|
||||
import { ComparisonContext } from "./FilterQueryParser";
|
||||
import { InClauseContext } from "./FilterQueryParser";
|
||||
import { NotInClauseContext } from "./FilterQueryParser";
|
||||
import { ValueListContext } from "./FilterQueryParser";
|
||||
import { FullTextContext } from "./FilterQueryParser";
|
||||
import { FunctionCallContext } from "./FilterQueryParser";
|
||||
import { FunctionParamListContext } from "./FilterQueryParser";
|
||||
import { FunctionParamContext } from "./FilterQueryParser";
|
||||
import { ArrayContext } from "./FilterQueryParser";
|
||||
import { ValueContext } from "./FilterQueryParser";
|
||||
import { KeyContext } from "./FilterQueryParser";
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
.trigger {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
[data-truncated='true'] {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tooltipContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
max-width: 480px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
margin: 0;
|
||||
max-height: 220px;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-family: var(--font-family-mono, monospace);
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.expandButton {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
max-width: 80vw;
|
||||
width: 80vw;
|
||||
}
|
||||
|
||||
.fullValue {
|
||||
margin: 0;
|
||||
max-height: 70vh;
|
||||
overflow: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-family: var(--font-family-mono, monospace);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { Button } from '@signozhq/ui/button';
|
||||
import { DialogWrapper } from '@signozhq/ui/dialog';
|
||||
import {
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipRoot,
|
||||
TooltipTrigger,
|
||||
} from '@signozhq/ui/tooltip';
|
||||
import { Fullscreen } from '@signozhq/icons';
|
||||
|
||||
import styles from './ExpandableValue.module.scss';
|
||||
|
||||
const DEFAULT_THRESHOLD = 100;
|
||||
const DEFAULT_DIALOG_TITLE = 'Value';
|
||||
|
||||
const DEFAULT_Z_INDEX = 1100;
|
||||
|
||||
interface ExpandableValueProps {
|
||||
value: string;
|
||||
title?: string;
|
||||
threshold?: number;
|
||||
zIndex?: number;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
function ExpandableValue({
|
||||
value,
|
||||
title = DEFAULT_DIALOG_TITLE,
|
||||
threshold = DEFAULT_THRESHOLD,
|
||||
zIndex = DEFAULT_Z_INDEX,
|
||||
children,
|
||||
}: ExpandableValueProps): JSX.Element {
|
||||
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||
|
||||
if (value.length <= threshold) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<TooltipRoot>
|
||||
<TooltipTrigger asChild>
|
||||
<span className={styles.trigger}>{children}</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent
|
||||
className={styles.tooltipContent}
|
||||
side="top"
|
||||
style={{ zIndex }}
|
||||
>
|
||||
<pre className={styles.preview}>{value}</pre>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
prefix={<Fullscreen size={14} />}
|
||||
onClick={(): void => setIsDialogOpen(true)}
|
||||
className={styles.expandButton}
|
||||
>
|
||||
Expand
|
||||
</Button>
|
||||
</TooltipContent>
|
||||
</TooltipRoot>
|
||||
|
||||
<DialogWrapper
|
||||
title={title}
|
||||
open={isDialogOpen}
|
||||
onOpenChange={setIsDialogOpen}
|
||||
className={styles.dialog}
|
||||
style={{ zIndex }}
|
||||
>
|
||||
<pre className={styles.fullValue}>{value}</pre>
|
||||
</DialogWrapper>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExpandableValue;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './ExpandableValue';
|
||||
@@ -227,10 +227,8 @@ function PrettyView({
|
||||
menu={{ items: menuItems }}
|
||||
align="start"
|
||||
className="pretty-view-actions-dropdown"
|
||||
// onClick on the dropdown content is forwarded to the underlying div via ...props
|
||||
// but is not in the public type. Stop click bubbling so item clicks don't reach
|
||||
// clickable ancestors of the trigger through the React tree.
|
||||
// @ts-expect-error see comment above
|
||||
// Stop click bubbling so item clicks don't reach clickable ancestors
|
||||
// of the trigger through the React tree.
|
||||
onClick={(e: React.MouseEvent): void => e.stopPropagation()}
|
||||
>
|
||||
<span
|
||||
|
||||
@@ -13,8 +13,11 @@ import { useQuery } from 'react-query';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import { useGetHosts } from 'api/generated/services/zeus';
|
||||
import { useGetGlobalConfig } from 'api/generated/services/global';
|
||||
import { useGetMyUser } from 'api/generated/services/users';
|
||||
import listOrgPreferences from 'api/v1/org/preferences/list';
|
||||
import { clearAuthStorage } from 'utils/clearAuthStorage';
|
||||
import { getIsNoAuthMode, setNoAuthMode } from 'utils/noAuthMode';
|
||||
import listUserPreferences from 'api/v1/user/preferences/list';
|
||||
import getUserVersion from 'api/v1/version/get';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
@@ -70,11 +73,48 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
const [isLoggedIn, setIsLoggedIn] = useState<boolean>(
|
||||
(): boolean => getLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN) === 'true',
|
||||
);
|
||||
const [isPreflightLoading, setIsPreflightLoading] = useState<boolean>(true);
|
||||
const [org, setOrg] = useState<Organization[] | null>(null);
|
||||
const [changelog, setChangelog] = useState<ChangelogSchema | null>(null);
|
||||
|
||||
const [showChangelogModal, setShowChangelogModal] = useState<boolean>(false);
|
||||
|
||||
// Pre-flight: discover auth mode from public global config.
|
||||
// On success: in impersonation mode → clear stale tokens, force isLoggedIn=true,
|
||||
// set noAuthMode singleton so the axios interceptor (outside React)
|
||||
// can skip the rotate-logout chain.
|
||||
// On failure: fail-safe to normal auth flow (treat as not no-auth).
|
||||
const { data: globalConfigData, isLoading: isFetchingGlobalConfig } =
|
||||
useGetGlobalConfig({
|
||||
query: {
|
||||
retry: 2,
|
||||
retryDelay: 1000,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: Infinity,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (isFetchingGlobalConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
const impersonationEnabled =
|
||||
globalConfigData?.data?.identN?.impersonation?.enabled === true;
|
||||
|
||||
if (impersonationEnabled) {
|
||||
clearAuthStorage();
|
||||
setDefaultUser(getUserDefaults());
|
||||
setLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN, 'true');
|
||||
setNoAuthMode(true);
|
||||
setIsLoggedIn(true);
|
||||
} else {
|
||||
setNoAuthMode(false);
|
||||
}
|
||||
|
||||
setIsPreflightLoading(false);
|
||||
}, [globalConfigData, isFetchingGlobalConfig]);
|
||||
|
||||
// fetcher for current user
|
||||
// user will only be fetched if the user id and token is present
|
||||
// if logged out and trying to hit any route none of these calls will trigger
|
||||
@@ -89,14 +129,13 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
const {
|
||||
permissions: permissionsResult,
|
||||
isFetching: isFetchingPermissions,
|
||||
error: errorOnPermissions,
|
||||
refetchPermissions,
|
||||
} = useAuthZ([IsAdminPermission, IsEditorPermission, IsViewerPermission], {
|
||||
enabled: isLoggedIn,
|
||||
});
|
||||
|
||||
const isFetchingUser = isFetchingUserData || isFetchingPermissions;
|
||||
const userFetchError = userFetchDataError || errorOnPermissions;
|
||||
const userFetchError = userFetchDataError;
|
||||
|
||||
const userRole = useMemo(() => {
|
||||
if (permissionsResult?.[IsAdminPermission]?.isGranted) {
|
||||
@@ -366,6 +405,9 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
|
||||
// global event listener for LOGOUT event to clean the app context state
|
||||
useGlobalEventListener('LOGOUT', () => {
|
||||
if (getIsNoAuthMode()) {
|
||||
return;
|
||||
} // logout is meaningless in no-auth; defensively no-op
|
||||
setIsLoggedIn(false);
|
||||
setDefaultUser(getUserDefaults());
|
||||
setActiveLicense(null);
|
||||
@@ -385,6 +427,7 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
orgPreferences,
|
||||
hostsData,
|
||||
isLoggedIn,
|
||||
isPreflightLoading,
|
||||
org,
|
||||
isFetchingUser,
|
||||
isFetchingActiveLicense,
|
||||
@@ -425,6 +468,7 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
isLoggedIn,
|
||||
hostsData,
|
||||
hostsFetchError,
|
||||
isPreflightLoading,
|
||||
org,
|
||||
orgPreferences,
|
||||
activeLicenseRefetch,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ReactElement } from 'react';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import { getIsNoAuthMode, setNoAuthMode } from 'utils/noAuthMode';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { SINGLE_FLIGHT_WAIT_TIME_MS } from 'hooks/useAuthZ/constants';
|
||||
import { server } from 'mocks-server/server';
|
||||
@@ -13,6 +14,7 @@ import { AppProvider, useAppContext } from '../App';
|
||||
|
||||
const MY_USER_URL = 'http://localhost/api/v2/users/me';
|
||||
const MY_ORG_URL = 'http://localhost/api/v2/orgs/me';
|
||||
const GLOBAL_CONFIG_URL = 'http://localhost/api/v1/global/config';
|
||||
|
||||
jest.mock('constants/env', () => ({
|
||||
ENVIRONMENT: { baseURL: 'http://localhost', wsURL: '' },
|
||||
@@ -296,9 +298,32 @@ describe('AppProvider when authz/check fails', () => {
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
setLocalStorageApi(LOCALSTORAGE.IS_LOGGED_IN, 'true');
|
||||
server.use(
|
||||
rest.get(MY_USER_URL, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: {
|
||||
id: 'u-1',
|
||||
displayName: 'Test User',
|
||||
email: 'test@signoz.io',
|
||||
orgId: 'org-1',
|
||||
isRoot: false,
|
||||
status: 'active',
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
rest.get(MY_ORG_URL, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({ data: { id: 'org-1', displayName: 'Org' } }),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('sets userFetchError when authz/check returns 500 (same as user fetch error)', async () => {
|
||||
it('does not set userFetchError when authz/check returns 500 (authz errors are ignored)', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, (_, res, ctx) =>
|
||||
res(ctx.status(500), ctx.json({ error: 'Internal Server Error' })),
|
||||
@@ -312,13 +337,13 @@ describe('AppProvider when authz/check fails', () => {
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.userFetchError).toBeTruthy();
|
||||
expect(result.current.userFetchError).toBeFalsy();
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
|
||||
it('sets userFetchError when authz/check fails with network error (same as user fetch error)', async () => {
|
||||
it('does not set userFetchError when authz/check fails with network error (authz errors are ignored)', async () => {
|
||||
server.use(
|
||||
rest.post(AUTHZ_CHECK_URL, (_, res) => res.networkError('Network error')),
|
||||
);
|
||||
@@ -330,9 +355,132 @@ describe('AppProvider when authz/check fails', () => {
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.userFetchError).toBeTruthy();
|
||||
expect(result.current.userFetchError).toBeFalsy();
|
||||
},
|
||||
{ timeout: 2000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AppProvider no-auth preflight', () => {
|
||||
beforeEach(() => {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
setNoAuthMode(false);
|
||||
});
|
||||
|
||||
it('sets noAuthMode singleton when impersonation is enabled', async () => {
|
||||
server.use(
|
||||
rest.get(GLOBAL_CONFIG_URL, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: { identN: { impersonation: { enabled: true } } },
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.isPreflightLoading).toBe(false);
|
||||
},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
|
||||
expect(getIsNoAuthMode()).toBe(true);
|
||||
});
|
||||
|
||||
it('leaves noAuthMode singleton false when impersonation is disabled', async () => {
|
||||
server.use(
|
||||
rest.get(GLOBAL_CONFIG_URL, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: { identN: { impersonation: { enabled: false } } },
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.isPreflightLoading).toBe(false);
|
||||
},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
|
||||
expect(getIsNoAuthMode()).toBe(false);
|
||||
});
|
||||
|
||||
it('clears stale auth tokens from localStorage and resets in-memory JWT state when impersonation is enabled', async () => {
|
||||
setLocalStorageApi(LOCALSTORAGE.AUTH_TOKEN, 'stale-access-token');
|
||||
setLocalStorageApi(LOCALSTORAGE.REFRESH_AUTH_TOKEN, 'stale-refresh-token');
|
||||
setLocalStorageApi(LOCALSTORAGE.LOGGED_IN_USER_EMAIL, 'old@example.com');
|
||||
setLocalStorageApi(LOCALSTORAGE.LOGGED_IN_USER_NAME, 'Old Name');
|
||||
|
||||
server.use(
|
||||
rest.get(GLOBAL_CONFIG_URL, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: { identN: { impersonation: { enabled: true } } },
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.isPreflightLoading).toBe(false);
|
||||
},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
|
||||
// localStorage cleared
|
||||
expect(localStorage.getItem(LOCALSTORAGE.AUTH_TOKEN)).toBeNull();
|
||||
expect(localStorage.getItem(LOCALSTORAGE.REFRESH_AUTH_TOKEN)).toBeNull();
|
||||
expect(localStorage.getItem(LOCALSTORAGE.LOGGED_IN_USER_EMAIL)).toBeNull();
|
||||
expect(localStorage.getItem(LOCALSTORAGE.LOGGED_IN_USER_NAME)).toBeNull();
|
||||
|
||||
// in-memory JWTs reset so stale tokens don't linger in context or React Query keys
|
||||
expect(result.current.user.accessJwt).toBe('');
|
||||
expect(result.current.user.refreshJwt).toBe('');
|
||||
});
|
||||
|
||||
it('transitions isPreflightLoading from true to false once preflight resolves', async () => {
|
||||
server.use(
|
||||
rest.get(GLOBAL_CONFIG_URL, (_, res, ctx) =>
|
||||
res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
data: { identN: { impersonation: { enabled: false } } },
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const wrapper = createWrapper();
|
||||
const { result } = renderHook(() => useAppContext(), { wrapper });
|
||||
|
||||
expect(result.current.isPreflightLoading).toBe(true);
|
||||
|
||||
await waitFor(
|
||||
() => {
|
||||
expect(result.current.isPreflightLoading).toBe(false);
|
||||
},
|
||||
{ timeout: 3000 },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface IAppContext {
|
||||
userPreferences: UserPreference[] | null;
|
||||
hostsData: GetHosts200 | null;
|
||||
isLoggedIn: boolean;
|
||||
isPreflightLoading: boolean;
|
||||
org: Organization[] | null;
|
||||
isFetchingUser: boolean;
|
||||
isFetchingActiveLicense: boolean;
|
||||
|
||||
@@ -243,6 +243,7 @@ export function getAppContextMock(
|
||||
isFetchingOrgPreferences: false,
|
||||
orgPreferencesFetchError: null,
|
||||
isLoggedIn: true,
|
||||
isPreflightLoading: false,
|
||||
showChangelogModal: false,
|
||||
updateUser: jest.fn(),
|
||||
updateOrg: jest.fn(),
|
||||
|
||||
39
frontend/src/utils/__tests__/clearAuthStorage.test.ts
Normal file
39
frontend/src/utils/__tests__/clearAuthStorage.test.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
|
||||
import { clearAuthStorage } from '../clearAuthStorage';
|
||||
|
||||
describe('clearAuthStorage', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear();
|
||||
});
|
||||
|
||||
it('removes all auth-related localStorage keys', () => {
|
||||
localStorage.setItem(LOCALSTORAGE.AUTH_TOKEN, 'access');
|
||||
localStorage.setItem(LOCALSTORAGE.REFRESH_AUTH_TOKEN, 'refresh');
|
||||
localStorage.setItem(LOCALSTORAGE.IS_LOGGED_IN, 'true');
|
||||
localStorage.setItem(LOCALSTORAGE.LOGGED_IN_USER_EMAIL, 'old@example.com');
|
||||
localStorage.setItem(LOCALSTORAGE.LOGGED_IN_USER_NAME, 'old');
|
||||
localStorage.setItem(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
|
||||
localStorage.setItem(LOCALSTORAGE.USER_ID, 'abc');
|
||||
|
||||
clearAuthStorage();
|
||||
|
||||
expect(localStorage.getItem(LOCALSTORAGE.AUTH_TOKEN)).toBeNull();
|
||||
expect(localStorage.getItem(LOCALSTORAGE.REFRESH_AUTH_TOKEN)).toBeNull();
|
||||
expect(localStorage.getItem(LOCALSTORAGE.IS_LOGGED_IN)).toBeNull();
|
||||
expect(localStorage.getItem(LOCALSTORAGE.LOGGED_IN_USER_EMAIL)).toBeNull();
|
||||
expect(localStorage.getItem(LOCALSTORAGE.LOGGED_IN_USER_NAME)).toBeNull();
|
||||
expect(localStorage.getItem(LOCALSTORAGE.IS_IDENTIFIED_USER)).toBeNull();
|
||||
expect(localStorage.getItem(LOCALSTORAGE.USER_ID)).toBeNull();
|
||||
});
|
||||
|
||||
it('preserves non-auth localStorage keys', () => {
|
||||
localStorage.setItem(LOCALSTORAGE.THEME, 'dark');
|
||||
localStorage.setItem(LOCALSTORAGE.AUTH_TOKEN, 'access');
|
||||
|
||||
clearAuthStorage();
|
||||
|
||||
expect(localStorage.getItem(LOCALSTORAGE.THEME)).toBe('dark');
|
||||
expect(localStorage.getItem(LOCALSTORAGE.AUTH_TOKEN)).toBeNull();
|
||||
});
|
||||
});
|
||||
16
frontend/src/utils/clearAuthStorage.ts
Normal file
16
frontend/src/utils/clearAuthStorage.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import deleteLocalStorageKey from 'api/browser/localstorage/remove';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
|
||||
const AUTH_KEYS: LOCALSTORAGE[] = [
|
||||
LOCALSTORAGE.AUTH_TOKEN,
|
||||
LOCALSTORAGE.REFRESH_AUTH_TOKEN,
|
||||
LOCALSTORAGE.IS_LOGGED_IN,
|
||||
LOCALSTORAGE.LOGGED_IN_USER_EMAIL,
|
||||
LOCALSTORAGE.LOGGED_IN_USER_NAME,
|
||||
LOCALSTORAGE.IS_IDENTIFIED_USER,
|
||||
LOCALSTORAGE.USER_ID,
|
||||
];
|
||||
|
||||
export const clearAuthStorage = (): void => {
|
||||
AUTH_KEYS.forEach((key) => deleteLocalStorageKey(key));
|
||||
};
|
||||
7
frontend/src/utils/noAuthMode.ts
Normal file
7
frontend/src/utils/noAuthMode.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
let _isNoAuthMode = false;
|
||||
|
||||
export const setNoAuthMode = (value: boolean): void => {
|
||||
_isNoAuthMode = value;
|
||||
};
|
||||
|
||||
export const getIsNoAuthMode = (): boolean => _isNoAuthMode;
|
||||
@@ -107,7 +107,7 @@ fullText
|
||||
* ...
|
||||
*/
|
||||
functionCall
|
||||
: (HASTOKEN | HAS | HASANY | HASALL | SEARCH) LPAREN functionParamList RPAREN
|
||||
: (HASTOKEN | HAS | HASANY | HASALL) LPAREN functionParamList RPAREN
|
||||
;
|
||||
|
||||
// Function parameters can be keys, single scalar values, or arrays
|
||||
@@ -184,7 +184,6 @@ HASTOKEN : [Hh][Aa][Ss][Tt][Oo][Kk][Ee][Nn];
|
||||
HAS : [Hh][Aa][Ss] ;
|
||||
HASANY : [Hh][Aa][Ss][Aa][Nn][Yy] ;
|
||||
HASALL : [Hh][Aa][Ss][Aa][Ll][Ll] ;
|
||||
SEARCH : [Ss][Ee][Aa][Rr][Cc][Hh] ;
|
||||
|
||||
// Potential boolean constants
|
||||
BOOL
|
||||
|
||||
@@ -26,7 +26,7 @@ func buildClusterRecords(
|
||||
records := make([]inframonitoringtypes.ClusterRecord, 0, len(pageGroups))
|
||||
for _, labels := range pageGroups {
|
||||
compositeKey := compositeKeyFromLabels(labels, groupBy)
|
||||
clusterName := labels[clusterNameAttrKey]
|
||||
clusterName := labels[inframonitoringtypes.ClusterNameAttrKey]
|
||||
|
||||
record := inframonitoringtypes.ClusterRecord{ // initialize with default values
|
||||
ClusterName: clusterName,
|
||||
@@ -87,6 +87,9 @@ func (m *module) getTopClusterGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.ClusterNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.ClusterNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToClustersQueryNames[orderByKey]
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
|
||||
|
||||
@@ -7,14 +7,9 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
// TODO(nikhilmantri0902): change to k8s.cluster.uid after showing the missing
|
||||
// data banner. Carried forward from v1 (see k8sClusterUIDAttrKey in
|
||||
// pkg/query-service/app/inframetrics/clusters.go).
|
||||
const clusterNameAttrKey = "k8s.cluster.name"
|
||||
|
||||
var clusterNameGroupByKey = qbtypes.GroupByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: clusterNameAttrKey,
|
||||
Name: inframonitoringtypes.ClusterNameAttrKey,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ func buildDaemonSetRecords(
|
||||
records := make([]inframonitoringtypes.DaemonSetRecord, 0, len(pageGroups))
|
||||
for _, labels := range pageGroups {
|
||||
compositeKey := compositeKeyFromLabels(labels, groupBy)
|
||||
daemonSetName := labels[daemonSetNameAttrKey]
|
||||
daemonSetName := labels[inframonitoringtypes.DaemonSetNameAttrKey]
|
||||
|
||||
record := inframonitoringtypes.DaemonSetRecord{ // initialize with default values
|
||||
DaemonSetName: daemonSetName,
|
||||
@@ -95,6 +95,9 @@ func (m *module) getTopDaemonSetGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.DaemonSetNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.DaemonSetNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToDaemonSetsQueryNames[orderByKey]
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
|
||||
|
||||
@@ -7,14 +7,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
const (
|
||||
daemonSetNameAttrKey = "k8s.daemonset.name"
|
||||
daemonSetsBaseFilterExpr = "k8s.daemonset.name != ''"
|
||||
)
|
||||
const daemonSetsBaseFilterExpr = "k8s.daemonset.name != ''"
|
||||
|
||||
var daemonSetNameGroupByKey = qbtypes.GroupByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: daemonSetNameAttrKey,
|
||||
Name: inframonitoringtypes.DaemonSetNameAttrKey,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ func buildDeploymentRecords(
|
||||
records := make([]inframonitoringtypes.DeploymentRecord, 0, len(pageGroups))
|
||||
for _, labels := range pageGroups {
|
||||
compositeKey := compositeKeyFromLabels(labels, groupBy)
|
||||
deploymentName := labels[deploymentNameAttrKey]
|
||||
deploymentName := labels[inframonitoringtypes.DeploymentNameAttrKey]
|
||||
|
||||
record := inframonitoringtypes.DeploymentRecord{ // initialize with default values
|
||||
DeploymentName: deploymentName,
|
||||
@@ -95,6 +95,9 @@ func (m *module) getTopDeploymentGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.DeploymentNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.DeploymentNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToDeploymentsQueryNames[orderByKey]
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
|
||||
|
||||
@@ -7,14 +7,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
const (
|
||||
deploymentNameAttrKey = "k8s.deployment.name"
|
||||
deploymentsBaseFilterExpr = "k8s.deployment.name != ''"
|
||||
)
|
||||
const deploymentsBaseFilterExpr = "k8s.deployment.name != ''"
|
||||
|
||||
var deploymentNameGroupByKey = qbtypes.GroupByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: deploymentNameAttrKey,
|
||||
Name: inframonitoringtypes.DeploymentNameAttrKey,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
|
||||
@@ -38,7 +38,7 @@ func (m *module) getPerGroupHostStatusCounts(
|
||||
uint64(req.Start), uint64(req.End), nil,
|
||||
)
|
||||
|
||||
hostNameExpr := fmt.Sprintf("JSONExtractString(labels, '%s')", hostNameAttrKey)
|
||||
hostNameExpr := fmt.Sprintf("JSONExtractString(labels, '%s')", inframonitoringtypes.HostNameAttrKey)
|
||||
|
||||
sb := sqlbuilder.NewSelectBuilder()
|
||||
selectCols := make([]string, 0, len(req.GroupBy)+2)
|
||||
@@ -48,7 +48,7 @@ func (m *module) getPerGroupHostStatusCounts(
|
||||
)
|
||||
}
|
||||
|
||||
activeHostsSQ := m.getActiveHostsQuery(metricNames, hostNameAttrKey, sinceUnixMilli)
|
||||
activeHostsSQ := m.getActiveHostsQuery(metricNames, inframonitoringtypes.HostNameAttrKey, sinceUnixMilli)
|
||||
selectCols = append(selectCols,
|
||||
fmt.Sprintf("uniqExactIf(%s, %s GLOBAL IN (%s)) AS active_host_count", hostNameExpr, hostNameExpr, sb.Var(activeHostsSQ)),
|
||||
fmt.Sprintf("uniqExactIf(%s, %s != '') AS total_host_count", hostNameExpr, hostNameExpr),
|
||||
@@ -142,7 +142,7 @@ func buildHostRecords(
|
||||
records := make([]inframonitoringtypes.HostRecord, 0, len(pageGroups))
|
||||
for _, labels := range pageGroups {
|
||||
compositeKey := compositeKeyFromLabels(labels, groupBy)
|
||||
hostName := labels[hostNameAttrKey]
|
||||
hostName := labels[inframonitoringtypes.HostNameAttrKey]
|
||||
|
||||
activeStatus := inframonitoringtypes.HostStatusNone
|
||||
activeHostCount := 0
|
||||
@@ -216,6 +216,9 @@ func (m *module) getTopHostGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.HostNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.HostNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToHostsQueryNames[orderByKey]
|
||||
// The last entry is the formula/query whose value we sort by.
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
@@ -281,7 +284,7 @@ func (m *module) applyHostsActiveStatusFilter(req *inframonitoringtypes.Postable
|
||||
if req.Filter.FilterByStatus == inframonitoringtypes.HostStatusInactive {
|
||||
op = "NOT IN"
|
||||
}
|
||||
statusClause := fmt.Sprintf("%s %s (%s)", hostNameAttrKey, op, strings.Join(activeHosts, ", "))
|
||||
statusClause := fmt.Sprintf("%s %s (%s)", inframonitoringtypes.HostNameAttrKey, op, strings.Join(activeHosts, ", "))
|
||||
req.Filter.Expression = mergeFilterExpressions(req.Filter.Expression, statusClause)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -7,14 +7,10 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
const (
|
||||
hostNameAttrKey = "host.name"
|
||||
)
|
||||
|
||||
// Helper group-by key used across all queries.
|
||||
var hostNameGroupByKey = qbtypes.GroupByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: hostNameAttrKey,
|
||||
Name: inframonitoringtypes.HostNameAttrKey,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
|
||||
@@ -25,7 +25,7 @@ func buildJobRecords(
|
||||
records := make([]inframonitoringtypes.JobRecord, 0, len(pageGroups))
|
||||
for _, labels := range pageGroups {
|
||||
compositeKey := compositeKeyFromLabels(labels, groupBy)
|
||||
jobName := labels[jobNameAttrKey]
|
||||
jobName := labels[inframonitoringtypes.JobNameAttrKey]
|
||||
|
||||
record := inframonitoringtypes.JobRecord{ // initialize with default values
|
||||
JobName: jobName,
|
||||
@@ -103,6 +103,9 @@ func (m *module) getTopJobGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.JobNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.JobNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToJobsQueryNames[orderByKey]
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
|
||||
|
||||
@@ -7,14 +7,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
const (
|
||||
jobNameAttrKey = "k8s.job.name"
|
||||
jobsBaseFilterExpr = "k8s.job.name != ''"
|
||||
)
|
||||
const jobsBaseFilterExpr = "k8s.job.name != ''"
|
||||
|
||||
var jobNameGroupByKey = qbtypes.GroupByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: jobNameAttrKey,
|
||||
Name: inframonitoringtypes.JobNameAttrKey,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
|
||||
@@ -100,7 +100,7 @@ func (m *module) ListHosts(ctx context.Context, orgID valuer.UUID, req *inframon
|
||||
// Determine active hosts: those with metrics reported in the last 10 minutes.
|
||||
// Compute the cutoff once so every downstream query/subquery agrees on what "active" means.
|
||||
sinceUnixMilli := time.Now().Add(-10 * time.Minute).UTC().UnixMilli()
|
||||
activeHostsMap, err := m.getActiveHosts(ctx, hostsTableMetricNamesList, hostNameAttrKey, sinceUnixMilli)
|
||||
activeHostsMap, err := m.getActiveHosts(ctx, hostsTableMetricNamesList, inframonitoringtypes.HostNameAttrKey, sinceUnixMilli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -146,7 +146,7 @@ func (m *module) ListHosts(ctx context.Context, orgID valuer.UUID, req *inframon
|
||||
// When host.name is not in groupBy, we need to run an additional query to get the counts per group for the current page,
|
||||
// using the same filter expression as the main query (including user filters + page groups IN clause).
|
||||
hostCounts := make(map[string]groupHostStatusCounts)
|
||||
isHostNameInGroupBy := isKeyInGroupByAttrs(req.GroupBy, hostNameAttrKey)
|
||||
isHostNameInGroupBy := isKeyInGroupByAttrs(req.GroupBy, inframonitoringtypes.HostNameAttrKey)
|
||||
if !isHostNameInGroupBy {
|
||||
hostCounts, err = m.getPerGroupHostStatusCounts(ctx, req, hostsTableMetricNamesList, pageGroups, sinceUnixMilli)
|
||||
if err != nil {
|
||||
@@ -324,7 +324,7 @@ func (m *module) ListNodes(ctx context.Context, orgID valuer.UUID, req *inframon
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isNodeNameInGroupBy := isKeyInGroupByAttrs(req.GroupBy, nodeNameAttrKey)
|
||||
isNodeNameInGroupBy := isKeyInGroupByAttrs(req.GroupBy, inframonitoringtypes.NodeNameAttrKey)
|
||||
resp.Records = buildNodeRecords(isNodeNameInGroupBy, queryResp, pageGroups, req.GroupBy, metadataMap, nodeConditionCounts, podPhaseCounts)
|
||||
resp.Warning = queryResp.Warning
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ func buildNamespaceRecords(
|
||||
records := make([]inframonitoringtypes.NamespaceRecord, 0, len(pageGroups))
|
||||
for _, labels := range pageGroups {
|
||||
compositeKey := compositeKeyFromLabels(labels, groupBy)
|
||||
namespaceName := labels[namespaceNameAttrKey]
|
||||
namespaceName := labels[inframonitoringtypes.NamespaceNameAttrKey]
|
||||
|
||||
record := inframonitoringtypes.NamespaceRecord{ // initialize with default values
|
||||
NamespaceName: namespaceName,
|
||||
@@ -70,6 +70,9 @@ func (m *module) getTopNamespaceGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.NamespaceNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.NamespaceNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToNamespacesQueryNames[orderByKey]
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
|
||||
|
||||
@@ -7,13 +7,9 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
const (
|
||||
namespaceNameAttrKey = "k8s.namespace.name"
|
||||
)
|
||||
|
||||
var namespaceNameGroupByKey = qbtypes.GroupByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: namespaceNameAttrKey,
|
||||
Name: inframonitoringtypes.NamespaceNameAttrKey,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@ func buildNodeRecords(
|
||||
records := make([]inframonitoringtypes.NodeRecord, 0, len(pageGroups))
|
||||
for _, labels := range pageGroups {
|
||||
compositeKey := compositeKeyFromLabels(labels, groupBy)
|
||||
nodeName := labels[nodeNameAttrKey]
|
||||
nodeName := labels[inframonitoringtypes.NodeNameAttrKey]
|
||||
|
||||
record := inframonitoringtypes.NodeRecord{ // initialize with default values
|
||||
NodeName: nodeName,
|
||||
@@ -105,6 +105,9 @@ func (m *module) getTopNodeGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.NodeNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.NodeNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToNodesQueryNames[orderByKey]
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
|
||||
@@ -201,7 +204,7 @@ func (m *module) getPerGroupNodeConditionCounts(
|
||||
timeSeriesFPs := sqlbuilder.NewSelectBuilder()
|
||||
timeSeriesFPsSelectCols := []string{
|
||||
"fingerprint",
|
||||
fmt.Sprintf("JSONExtractString(labels, %s) AS node_name", timeSeriesFPs.Var(nodeNameAttrKey)),
|
||||
fmt.Sprintf("JSONExtractString(labels, %s) AS node_name", timeSeriesFPs.Var(inframonitoringtypes.NodeNameAttrKey)),
|
||||
}
|
||||
for _, key := range groupBy {
|
||||
timeSeriesFPsSelectCols = append(timeSeriesFPsSelectCols,
|
||||
|
||||
@@ -7,14 +7,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
const (
|
||||
nodeNameAttrKey = "k8s.node.name"
|
||||
nodeConditionMetricName = "k8s.node.condition_ready"
|
||||
)
|
||||
const nodeConditionMetricName = "k8s.node.condition_ready"
|
||||
|
||||
var nodeNameGroupByKey = qbtypes.GroupByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: nodeNameAttrKey,
|
||||
Name: inframonitoringtypes.NodeNameAttrKey,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
|
||||
@@ -124,6 +124,9 @@ func (m *module) getTopPodGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.PodNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.PodNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToPodsQueryNames[orderByKey]
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ func buildStatefulSetRecords(
|
||||
records := make([]inframonitoringtypes.StatefulSetRecord, 0, len(pageGroups))
|
||||
for _, labels := range pageGroups {
|
||||
compositeKey := compositeKeyFromLabels(labels, groupBy)
|
||||
statefulSetName := labels[statefulSetNameAttrKey]
|
||||
statefulSetName := labels[inframonitoringtypes.StatefulSetNameAttrKey]
|
||||
|
||||
record := inframonitoringtypes.StatefulSetRecord{ // initialize with default values
|
||||
StatefulSetName: statefulSetName,
|
||||
@@ -95,6 +95,9 @@ func (m *module) getTopStatefulSetGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.StatefulSetNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.StatefulSetNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToStatefulSetsQueryNames[orderByKey]
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
|
||||
|
||||
@@ -7,14 +7,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
const (
|
||||
statefulSetNameAttrKey = "k8s.statefulset.name"
|
||||
statefulSetsBaseFilterExpr = "k8s.statefulset.name != ''"
|
||||
)
|
||||
const statefulSetsBaseFilterExpr = "k8s.statefulset.name != ''"
|
||||
|
||||
var statefulSetNameGroupByKey = qbtypes.GroupByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: statefulSetNameAttrKey,
|
||||
Name: inframonitoringtypes.StatefulSetNameAttrKey,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ func buildVolumeRecords(
|
||||
records := make([]inframonitoringtypes.VolumeRecord, 0, len(pageGroups))
|
||||
for _, labels := range pageGroups {
|
||||
compositeKey := compositeKeyFromLabels(labels, groupBy)
|
||||
pvcName := labels[persistentVolumeClaimNameAttrKey]
|
||||
pvcName := labels[inframonitoringtypes.PersistentVolumeClaimNameAttrKey]
|
||||
|
||||
record := inframonitoringtypes.VolumeRecord{ // initialize with default values
|
||||
PersistentVolumeClaimName: pvcName,
|
||||
@@ -75,6 +75,9 @@ func (m *module) getTopVolumeGroups(
|
||||
metadataMap map[string]map[string]string,
|
||||
) ([]map[string]string, error) {
|
||||
orderByKey := req.OrderBy.Key.Name
|
||||
if orderByKey == inframonitoringtypes.PersistentVolumeClaimNameAttrKey {
|
||||
return inframonitoringtypes.PaginateMetadataByName(metadataMap, req.GroupBy, req.OrderBy.Direction, req.Offset, req.Limit, inframonitoringtypes.PersistentVolumeClaimNameAttrKey), nil
|
||||
}
|
||||
queryNamesForOrderBy := orderByToVolumesQueryNames[orderByKey]
|
||||
rankingQueryName := queryNamesForOrderBy[len(queryNamesForOrderBy)-1]
|
||||
|
||||
|
||||
@@ -7,14 +7,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
|
||||
const (
|
||||
persistentVolumeClaimNameAttrKey = "k8s.persistentvolumeclaim.name"
|
||||
volumesBaseFilterExpr = "k8s.persistentvolumeclaim.name != ''"
|
||||
)
|
||||
const volumesBaseFilterExpr = "k8s.persistentvolumeclaim.name != ''"
|
||||
|
||||
var pvcNameGroupByKey = qbtypes.GroupByKey{
|
||||
TelemetryFieldKey: telemetrytypes.TelemetryFieldKey{
|
||||
Name: persistentVolumeClaimNameAttrKey,
|
||||
Name: inframonitoringtypes.PersistentVolumeClaimNameAttrKey,
|
||||
FieldContext: telemetrytypes.FieldContextResource,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -24,13 +24,12 @@ HASTOKEN=23
|
||||
HAS=24
|
||||
HASANY=25
|
||||
HASALL=26
|
||||
SEARCH=27
|
||||
BOOL=28
|
||||
NUMBER=29
|
||||
QUOTED_TEXT=30
|
||||
KEY=31
|
||||
WS=32
|
||||
FREETEXT=33
|
||||
BOOL=27
|
||||
NUMBER=28
|
||||
QUOTED_TEXT=29
|
||||
KEY=30
|
||||
WS=31
|
||||
FREETEXT=32
|
||||
'('=1
|
||||
')'=2
|
||||
'['=3
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user