mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-18 16:07:10 +00:00
Merge branch 'SIG-6819' into SIG-6421
This commit is contained in:
commit
1b7f13bf1d
@ -23,6 +23,7 @@ import AlertRuleProvider from 'providers/Alert';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { IUser } from 'providers/App/types';
|
||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
@ -358,6 +359,7 @@ function App(): JSX.Element {
|
||||
<CompatRouter>
|
||||
<UserpilotRouteTracker />
|
||||
<NotificationProvider>
|
||||
<ErrorModalProvider>
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
@ -386,6 +388,7 @@ function App(): JSX.Element {
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
</ErrorModalProvider>
|
||||
</NotificationProvider>
|
||||
</CompatRouter>
|
||||
</Router>
|
||||
|
||||
191
frontend/src/assets/Error.tsx
Normal file
191
frontend/src/assets/Error.tsx
Normal file
@ -0,0 +1,191 @@
|
||||
import React from 'react';
|
||||
|
||||
type ErrorIconProps = React.SVGProps<SVGSVGElement>;
|
||||
|
||||
function ErrorIcon({ ...props }: ErrorIconProps): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
fill="#C62828"
|
||||
d="M1.281 5.78a.922.922 0 0 0-.92.921v4.265a.922.922 0 0 0 .92.92h.617V5.775l-.617.005ZM12.747 5.78c.508 0 .92.413.92.92v4.264a.923.923 0 0 1-.92.922h-.617V5.775l.617.004Z"
|
||||
/>
|
||||
<path
|
||||
fill="#90A4AE"
|
||||
d="M12.463 5.931 12.45 4.82a.867.867 0 0 0-.87-.861H7.34v-1.49a1.083 1.083 0 1 0-.68 0v1.496H2.42a.867.867 0 0 0-.864.86v7.976c.003.475.389.86.865.862h9.16a.868.868 0 0 0 .869-.862v-.82h.013v-6.05Z"
|
||||
/>
|
||||
<path
|
||||
fill="#C62828"
|
||||
d="M7 1.885a.444.444 0 1 1 0-.888.444.444 0 0 1 0 .888Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#a)"
|
||||
d="M4.795 10.379h4.412c.384 0 .696.312.696.697v.063a.697.697 0 0 1-.696.697H4.795a.697.697 0 0 1-.697-.697v-.063c0-.385.312-.697.697-.697Z"
|
||||
/>
|
||||
<path fill="url(#b)" d="M6.115 10.38h-.262v1.455h.262V10.38Z" />
|
||||
<path fill="url(#c)" d="M7.138 10.38h-.262v1.455h.262V10.38Z" />
|
||||
<path fill="url(#d)" d="M8.147 10.38h-.262v1.455h.262V10.38Z" />
|
||||
<path fill="url(#e)" d="M9.22 10.38h-.262v1.455h.262V10.38Z" />
|
||||
<path fill="url(#f)" d="M5.042 10.379H4.78v1.454h.262V10.38Z" />
|
||||
<path
|
||||
fill="#C62828"
|
||||
d="M7 9.367h-.593a.111.111 0 0 1-.098-.162l.304-.6.288-.532a.11.11 0 0 1 .195 0l.29.556.301.576a.11.11 0 0 1-.098.162H7Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#g)"
|
||||
d="M4.627 8.587a1.278 1.278 0 1 0 0-2.556 1.278 1.278 0 0 0 0 2.556Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#h)"
|
||||
fillRule="evenodd"
|
||||
d="M4.627 6.142a1.167 1.167 0 1 0 0 2.333 1.167 1.167 0 0 0 0-2.333ZM3.237 7.31a1.389 1.389 0 1 1 2.778 0 1.389 1.389 0 0 1-2.777 0Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<path
|
||||
fill="url(#i)"
|
||||
d="M9.333 6.028a1.278 1.278 0 1 0 0 2.556 1.278 1.278 0 0 0 0-2.556Z"
|
||||
/>
|
||||
<path
|
||||
fill="url(#j)"
|
||||
fillRule="evenodd"
|
||||
d="M7.944 7.306a1.39 1.39 0 0 1 2.778 0 1.389 1.389 0 0 1-2.778 0Zm1.39-1.167a1.167 1.167 0 1 0 0 2.334 1.167 1.167 0 0 0 0-2.334Z"
|
||||
clipRule="evenodd"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id="a"
|
||||
x1="7.001"
|
||||
x2="7.001"
|
||||
y1="11.836"
|
||||
y2="10.379"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset=".12" stopColor="#E0E0E0" />
|
||||
<stop offset=".52" stopColor="#fff" />
|
||||
<stop offset="1" stopColor="#EAEAEA" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="b"
|
||||
x1="5.984"
|
||||
x2="5.984"
|
||||
y1="11.835"
|
||||
y2="10.381"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333" />
|
||||
<stop offset=".55" stopColor="#666" />
|
||||
<stop offset="1" stopColor="#333" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="c"
|
||||
x1="7.007"
|
||||
x2="7.007"
|
||||
y1="11.835"
|
||||
y2="10.381"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333" />
|
||||
<stop offset=".55" stopColor="#666" />
|
||||
<stop offset="1" stopColor="#333" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="d"
|
||||
x1="8.016"
|
||||
x2="8.016"
|
||||
y1="11.835"
|
||||
y2="10.381"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333" />
|
||||
<stop offset=".55" stopColor="#666" />
|
||||
<stop offset="1" stopColor="#333" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="e"
|
||||
x1="9.089"
|
||||
x2="9.089"
|
||||
y1="11.835"
|
||||
y2="10.381"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333" />
|
||||
<stop offset=".55" stopColor="#666" />
|
||||
<stop offset="1" stopColor="#333" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="f"
|
||||
x1="4.911"
|
||||
x2="4.911"
|
||||
y1="11.833"
|
||||
y2="10.379"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333" />
|
||||
<stop offset=".55" stopColor="#666" />
|
||||
<stop offset="1" stopColor="#333" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="h"
|
||||
x1="3.238"
|
||||
x2="6.015"
|
||||
y1="7.309"
|
||||
y2="7.309"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333" />
|
||||
<stop offset=".55" stopColor="#666" />
|
||||
<stop offset="1" stopColor="#333" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="j"
|
||||
x1="7.939"
|
||||
x2="10.716"
|
||||
y1="7.306"
|
||||
y2="7.306"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333" />
|
||||
<stop offset=".55" stopColor="#666" />
|
||||
<stop offset="1" stopColor="#333" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
id="g"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="matrix(1.27771 0 0 1.2777 4.627 7.309)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset=".48" stopColor="#fff" />
|
||||
<stop offset=".77" stopColor="#FDFDFD" />
|
||||
<stop offset=".88" stopColor="#F6F6F6" />
|
||||
<stop offset=".96" stopColor="#EBEBEB" />
|
||||
<stop offset="1" stopColor="#E0E0E0" />
|
||||
</radialGradient>
|
||||
<radialGradient
|
||||
id="i"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientTransform="matrix(1.27771 0 0 1.2777 9.328 7.306)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop offset=".48" stopColor="#fff" />
|
||||
<stop offset=".77" stopColor="#FDFDFD" />
|
||||
<stop offset=".88" stopColor="#F6F6F6" />
|
||||
<stop offset=".96" stopColor="#EBEBEB" />
|
||||
<stop offset="1" stopColor="#E0E0E0" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorIcon;
|
||||
118
frontend/src/components/ErrorModal/ErrorModal.styles.scss
Normal file
118
frontend/src/components/ErrorModal/ErrorModal.styles.scss
Normal file
@ -0,0 +1,118 @@
|
||||
.error-modal {
|
||||
&__trigger {
|
||||
width: fit-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
border-radius: 20px;
|
||||
background: rgba(229, 72, 77, 0.2);
|
||||
padding-left: 3px;
|
||||
padding-right: 8px;
|
||||
cursor: pointer;
|
||||
span {
|
||||
color: var(--bg-cherry-500);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 20px; /* 200% */
|
||||
letter-spacing: 0.4px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
&__wrap {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(11, 12, 14, 0.12) 0.07%,
|
||||
rgba(39, 8, 14, 0.24) 50.04%,
|
||||
rgba(106, 29, 44, 0.36) 75.02%,
|
||||
rgba(197, 57, 85, 0.48) 87.51%,
|
||||
rgba(242, 71, 105, 0.6) 100%
|
||||
);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
backdrop-filter: blur(10px);
|
||||
|
||||
.ant-modal {
|
||||
bottom: 40px;
|
||||
top: unset;
|
||||
position: absolute;
|
||||
width: 520px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
&__body {
|
||||
padding: 0;
|
||||
background: var(--bg-ink-400);
|
||||
overflow: hidden;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
&__header {
|
||||
background: none !important;
|
||||
.ant-modal-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.key-value-label {
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
&__key,
|
||||
&__value {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0.48px;
|
||||
}
|
||||
&__key {
|
||||
text-transform: uppercase;
|
||||
&,
|
||||
&:hover {
|
||||
color: var(--bg-vanilla-100);
|
||||
}
|
||||
}
|
||||
&__value {
|
||||
color: var(--bg-vanilla-400);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
.close-button {
|
||||
padding: 3px 7px;
|
||||
background: var(--bg-ink-400);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--bg-slate-500);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
&__footer {
|
||||
margin: 0 !important;
|
||||
height: 6px;
|
||||
background: var(--bg-sakura-500);
|
||||
}
|
||||
&__content {
|
||||
padding: 0 !important;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.error-modal {
|
||||
&__body,
|
||||
&__header .close-button {
|
||||
background: var(--bg-vanilla-100);
|
||||
}
|
||||
&__header .close-button {
|
||||
svg {
|
||||
fill: var(--bg-vanilla-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
195
frontend/src/components/ErrorModal/ErrorModal.test.tsx
Normal file
195
frontend/src/components/ErrorModal/ErrorModal.test.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import ErrorModal from './ErrorModal';
|
||||
|
||||
// Mock the query client to return version data
|
||||
const mockVersionData = {
|
||||
payload: {
|
||||
ee: 'Y',
|
||||
version: '1.0.0',
|
||||
},
|
||||
};
|
||||
jest.mock('react-query', () => ({
|
||||
...jest.requireActual('react-query'),
|
||||
useQueryClient: (): { getQueryData: () => typeof mockVersionData } => ({
|
||||
getQueryData: jest.fn(() => mockVersionData),
|
||||
}),
|
||||
}));
|
||||
const mockError: APIError = new APIError({
|
||||
httpStatusCode: 400,
|
||||
error: {
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
message: 'Something went wrong while processing your request.',
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
code: 'An error occurred',
|
||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
||||
url: 'https://example.com/docs',
|
||||
errors: [
|
||||
{ message: 'First error detail' },
|
||||
{ message: 'Second error detail' },
|
||||
{ message: 'Third error detail' },
|
||||
],
|
||||
},
|
||||
});
|
||||
describe('ErrorModal Component', () => {
|
||||
it('should render the modal when open is true', () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
|
||||
// Check if the error message is displayed
|
||||
expect(screen.getByText('An error occurred')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('Something went wrong while processing your request.'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render the modal when open is false', () => {
|
||||
render(<ErrorModal error={mockError} open={false} onClose={jest.fn()} />);
|
||||
|
||||
// Check that the modal content is not in the document
|
||||
expect(screen.queryByText('An error occurred')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onClose when the close button is clicked', async () => {
|
||||
const onCloseMock = jest.fn();
|
||||
render(<ErrorModal error={mockError} open onClose={onCloseMock} />);
|
||||
|
||||
// Click the close button
|
||||
const closeButton = screen.getByTestId('close-button');
|
||||
act(() => {
|
||||
fireEvent.click(closeButton);
|
||||
});
|
||||
|
||||
// Check if onClose was called
|
||||
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should display version data if available', async () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
|
||||
// Check if the version data is displayed
|
||||
expect(screen.getByText('ENTERPRISE')).toBeInTheDocument();
|
||||
expect(screen.getByText('1.0.0')).toBeInTheDocument();
|
||||
});
|
||||
it('should render the messages count badge when there are multiple errors', () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
|
||||
// Check if the messages count badge is displayed
|
||||
expect(screen.getByText('MESSAGES')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('3')).toBeInTheDocument();
|
||||
|
||||
// Check if the individual error messages are displayed
|
||||
expect(screen.getByText('First error detail')).toBeInTheDocument();
|
||||
expect(screen.getByText('Second error detail')).toBeInTheDocument();
|
||||
expect(screen.getByText('Third error detail')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the open docs button when URL is provided', async () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
|
||||
// Check if the open docs button is displayed
|
||||
const openDocsButton = screen.getByTestId('error-docs-button');
|
||||
|
||||
expect(openDocsButton).toBeInTheDocument();
|
||||
|
||||
expect(openDocsButton).toHaveAttribute('href', 'https://example.com/docs');
|
||||
|
||||
expect(openDocsButton).toHaveAttribute('target', '_blank');
|
||||
});
|
||||
|
||||
it('should not display scroll for more if there are less than 10 messages', () => {
|
||||
render(<ErrorModal error={mockError} open onClose={jest.fn()} />);
|
||||
|
||||
expect(screen.queryByText('Scroll for more')).not.toBeInTheDocument();
|
||||
});
|
||||
it('should display scroll for more if there are more than 10 messages', async () => {
|
||||
const longError = new APIError({
|
||||
httpStatusCode: 400,
|
||||
error: {
|
||||
...mockError.error,
|
||||
code: 'An error occurred',
|
||||
message: 'Something went wrong while processing your request.',
|
||||
url: 'https://example.com/docs',
|
||||
errors: Array.from({ length: 15 }, (_, i) => ({
|
||||
message: `Error detail ${i + 1}`,
|
||||
})),
|
||||
},
|
||||
});
|
||||
|
||||
render(<ErrorModal error={longError} open onClose={jest.fn()} />);
|
||||
|
||||
// Check if the scroll hint is displayed
|
||||
expect(screen.getByText('Scroll for more')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
it('should render the trigger component if provided', () => {
|
||||
const mockTrigger = <button type="button">Open Error Modal</button>;
|
||||
render(
|
||||
<ErrorModal
|
||||
error={mockError}
|
||||
triggerComponent={mockTrigger}
|
||||
onClose={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Check if the trigger component is rendered
|
||||
expect(screen.getByText('Open Error Modal')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open the modal when the trigger component is clicked', async () => {
|
||||
const mockTrigger = <button type="button">Open Error Modal</button>;
|
||||
render(
|
||||
<ErrorModal
|
||||
error={mockError}
|
||||
triggerComponent={mockTrigger}
|
||||
onClose={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
// Click the trigger component
|
||||
const triggerButton = screen.getByText('Open Error Modal');
|
||||
act(() => {
|
||||
fireEvent.click(triggerButton);
|
||||
});
|
||||
|
||||
// Check if the modal is displayed
|
||||
expect(screen.getByText('An error occurred')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render the default trigger tag if no trigger component is provided', () => {
|
||||
render(<ErrorModal error={mockError} onClose={jest.fn()} />);
|
||||
|
||||
// Check if the default trigger tag is rendered
|
||||
expect(screen.getByText('error')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should close the modal when the onCancel event is triggered', async () => {
|
||||
const onCloseMock = jest.fn();
|
||||
render(<ErrorModal error={mockError} onClose={onCloseMock} />);
|
||||
|
||||
// Click the trigger component
|
||||
const triggerButton = screen.getByText('error');
|
||||
act(() => {
|
||||
fireEvent.click(triggerButton);
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('An error occurred')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Trigger the onCancel event
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByTestId('close-button'));
|
||||
});
|
||||
|
||||
// Check if the modal is closed
|
||||
expect(onCloseMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
await waitFor(() => {
|
||||
// check if the modal is not visible
|
||||
const modal = document.getElementsByClassName('ant-modal');
|
||||
const style = window.getComputedStyle(modal[0]);
|
||||
expect(style.display).toBe('none');
|
||||
});
|
||||
});
|
||||
102
frontend/src/components/ErrorModal/ErrorModal.tsx
Normal file
102
frontend/src/components/ErrorModal/ErrorModal.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import './ErrorModal.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Modal, Tag } from 'antd';
|
||||
import { CircleAlert, X } from 'lucide-react';
|
||||
import KeyValueLabel from 'periscope/components/KeyValueLabel';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import React from 'react';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
import ErrorContent from './components/ErrorContent';
|
||||
|
||||
type Props = {
|
||||
error: APIError;
|
||||
triggerComponent?: React.ReactElement;
|
||||
onClose?: () => void;
|
||||
open?: boolean;
|
||||
};
|
||||
|
||||
const classNames = {
|
||||
body: 'error-modal__body',
|
||||
mask: 'error-modal__mask',
|
||||
header: 'error-modal__header',
|
||||
footer: 'error-modal__footer',
|
||||
content: 'error-modal__content',
|
||||
};
|
||||
|
||||
function ErrorModal({
|
||||
open,
|
||||
error,
|
||||
triggerComponent,
|
||||
onClose,
|
||||
}: Props): JSX.Element {
|
||||
const [visible, setVisible] = React.useState(open);
|
||||
|
||||
const handleClose = (): void => {
|
||||
setVisible(false);
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
const { versionData } = useAppContext();
|
||||
|
||||
const versionDataPayload = versionData;
|
||||
|
||||
return (
|
||||
<>
|
||||
{!triggerComponent ? (
|
||||
<Tag
|
||||
className="error-modal__trigger"
|
||||
icon={<CircleAlert size={14} color={Color.BG_CHERRY_500} />}
|
||||
color="error"
|
||||
onClick={(): void => setVisible(true)}
|
||||
>
|
||||
error
|
||||
</Tag>
|
||||
) : (
|
||||
React.cloneElement(triggerComponent, {
|
||||
onClick: () => setVisible(true),
|
||||
})
|
||||
)}
|
||||
|
||||
<Modal
|
||||
open={visible}
|
||||
footer={<div className="error-modal__footer" />}
|
||||
title={
|
||||
<>
|
||||
{versionDataPayload ? (
|
||||
<KeyValueLabel
|
||||
badgeKey={versionDataPayload.ee === 'Y' ? 'ENTERPRISE' : 'COMMUNITY'}
|
||||
badgeValue={versionDataPayload.version}
|
||||
/>
|
||||
) : (
|
||||
<div className="error-modal__version-placeholder" />
|
||||
)}
|
||||
<Button
|
||||
type="default"
|
||||
className="close-button"
|
||||
onClick={handleClose}
|
||||
data-testid="close-button"
|
||||
>
|
||||
<X size={16} color={Color.BG_VANILLA_400} />
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
onCancel={handleClose}
|
||||
closeIcon={false}
|
||||
classNames={classNames}
|
||||
wrapClassName="error-modal__wrap"
|
||||
>
|
||||
<ErrorContent error={error} />
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ErrorModal.defaultProps = {
|
||||
onClose: undefined,
|
||||
triggerComponent: null,
|
||||
open: false,
|
||||
};
|
||||
|
||||
export default ErrorModal;
|
||||
@ -0,0 +1,208 @@
|
||||
.error-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// === SECTION: Summary (Top)
|
||||
&__summary-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid var(--bg-slate-400);
|
||||
}
|
||||
|
||||
&__summary {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
&__summary-left {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__summary-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__error-code {
|
||||
color: var(--bg-vanilla-100);
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 24px; /* 150% */
|
||||
letter-spacing: -0.08px;
|
||||
}
|
||||
|
||||
&__error-message {
|
||||
margin: 0;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
&__docs-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 9px 12.5px;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 150% */
|
||||
letter-spacing: 0.12px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--bg-slate-400);
|
||||
background: var(--bg-ink-300);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&__message-badge {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 0px 16px 16px;
|
||||
|
||||
.key-value-label {
|
||||
width: fit-content;
|
||||
border-color: var(--bg-slate-400);
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
&__key {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
&__value {
|
||||
padding-right: 10px;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 18px; /* 150% */
|
||||
letter-spacing: 0.48px;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
&-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
&-dot {
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
background: var(--bg-sakura-500);
|
||||
border-radius: 50%;
|
||||
}
|
||||
&-text {
|
||||
color: var(--bg-vanilla-100);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
line-height: 18px; /* 180% */
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
&-line {
|
||||
flex: 1;
|
||||
height: 8px;
|
||||
background-image: radial-gradient(circle, #444c63 1px, transparent 2px);
|
||||
background-size: 8px 11px;
|
||||
background-position: top left;
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
// === SECTION: Message List (Bottom)
|
||||
|
||||
&__message-list-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__message-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
max-height: 275px;
|
||||
}
|
||||
|
||||
&__message-item {
|
||||
position: relative;
|
||||
margin-bottom: 4px;
|
||||
color: var(--bg-vanilla-400);
|
||||
font-family: Geist Mono;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
color: var(--bg-vanilla-400);
|
||||
padding: 3px 12px;
|
||||
padding-left: 26px;
|
||||
}
|
||||
|
||||
&__message-item::before {
|
||||
font-family: unset;
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 2px;
|
||||
height: 4px;
|
||||
border-radius: 50px;
|
||||
background: var(--bg-slate-400);
|
||||
}
|
||||
|
||||
&__scroll-hint {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
margin: auto;
|
||||
width: fit-content;
|
||||
display: inline-flex;
|
||||
padding: 4px 12px 4px 10px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
background: var(--bg-slate-200);
|
||||
border-radius: 20px;
|
||||
box-shadow: 0px 103px 12px 0px rgba(0, 0, 0, 0.01),
|
||||
0px 66px 18px 0px rgba(0, 0, 0, 0.01), 0px 37px 22px 0px rgba(0, 0, 0, 0.03),
|
||||
0px 17px 17px 0px rgba(0, 0, 0, 0.04), 0px 4px 9px 0px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
&__scroll-hint-text {
|
||||
color: var(--bg-vanilla-100);
|
||||
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.error-content {
|
||||
&__error-code {
|
||||
color: var(--bg-ink-100);
|
||||
}
|
||||
&__error-message {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
&__message-item {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
&__message-badge {
|
||||
&-label-text {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
.key-value-label__value {
|
||||
color: var(--bg-ink-400);
|
||||
}
|
||||
}
|
||||
&__docs-button {
|
||||
background: var(--bg-vanilla-100);
|
||||
color: var(--bg-ink-100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,98 @@
|
||||
import './ErrorContent.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import ErrorIcon from 'assets/Error';
|
||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||
import { BookOpenText, ChevronsDown } from 'lucide-react';
|
||||
import KeyValueLabel from 'periscope/components/KeyValueLabel';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
interface ErrorContentProps {
|
||||
error: APIError;
|
||||
}
|
||||
|
||||
function ErrorContent({ error }: ErrorContentProps): JSX.Element {
|
||||
const {
|
||||
url: errorUrl,
|
||||
errors: errorMessages,
|
||||
code: errorCode,
|
||||
message: errorMessage,
|
||||
} = error.error.error;
|
||||
return (
|
||||
<section className="error-content">
|
||||
{/* Summary Header */}
|
||||
<section className="error-content__summary-section">
|
||||
<header className="error-content__summary">
|
||||
<div className="error-content__summary-left">
|
||||
<div className="error-content__icon-wrapper">
|
||||
<ErrorIcon />
|
||||
</div>
|
||||
|
||||
<div className="error-content__summary-text">
|
||||
<h2 className="error-content__error-code">{errorCode}</h2>
|
||||
<p className="error-content__error-message">{errorMessage}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{errorUrl && (
|
||||
<div className="error-content__summary-right">
|
||||
<Button
|
||||
type="default"
|
||||
className="error-content__docs-button"
|
||||
href={errorUrl}
|
||||
target="_blank"
|
||||
data-testid="error-docs-button"
|
||||
>
|
||||
<BookOpenText size={14} />
|
||||
Open Docs
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{errorMessages?.length > 0 && (
|
||||
<div className="error-content__message-badge">
|
||||
<KeyValueLabel
|
||||
badgeKey={
|
||||
<div className="error-content__message-badge-label">
|
||||
<div className="error-content__message-badge-label-dot" />
|
||||
<div className="error-content__message-badge-label-text">MESSAGES</div>
|
||||
</div>
|
||||
}
|
||||
badgeValue={errorMessages.length.toString()}
|
||||
/>
|
||||
<div className="error-content__message-badge-line" />
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Detailed Messages */}
|
||||
<section className="error-content__messages-section">
|
||||
<div className="error-content__message-list-container">
|
||||
<OverlayScrollbar>
|
||||
<ul className="error-content__message-list">
|
||||
{errorMessages?.map((error) => (
|
||||
<li className="error-content__message-item" key={error.message}>
|
||||
{error.message}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</OverlayScrollbar>
|
||||
{errorMessages?.length > 10 && (
|
||||
<div className="error-content__scroll-hint">
|
||||
<ChevronsDown
|
||||
size={16}
|
||||
color={Color.BG_VANILLA_100}
|
||||
className="error-content__scroll-hint-icon"
|
||||
/>
|
||||
<span className="error-content__scroll-hint-text">Scroll for more</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default ErrorContent;
|
||||
@ -37,6 +37,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@ -67,6 +68,7 @@ function HostMetricsDetails({
|
||||
AppState,
|
||||
GlobalReducer
|
||||
>((state) => state.globalTime);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const startMs = useMemo(() => Math.floor(Number(minTime) / 1000000000), [
|
||||
minTime,
|
||||
@ -86,7 +88,9 @@ function HostMetricsDetails({
|
||||
selectedTime as Time,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(
|
||||
(searchParams.get('view') as VIEWS) || VIEWS.METRICS,
|
||||
);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
@ -149,6 +153,9 @@ function HostMetricsDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
if (host?.hostName) {
|
||||
setSearchParams({ hostName: host?.hostName, view: e.target.value });
|
||||
}
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.HostEntity,
|
||||
view: e.target.value,
|
||||
@ -313,6 +320,7 @@ function HostMetricsDetails({
|
||||
|
||||
const handleClose = (): void => {
|
||||
setSelectedInterval(selectedTime as Time);
|
||||
setSearchParams({});
|
||||
|
||||
if (selectedTime !== 'custom') {
|
||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
||||
|
||||
@ -75,7 +75,6 @@ function HostMetricsLogs({ timeRange, filters }: Props): JSX.Element {
|
||||
const getItemContent = useCallback(
|
||||
(_: number, logToRender: ILog): JSX.Element => (
|
||||
<RawLogView
|
||||
isReadOnly
|
||||
isTextOverflowEllipsisDisabled
|
||||
key={logToRender.id}
|
||||
data={logToRender}
|
||||
|
||||
@ -80,6 +80,7 @@ function Metrics({
|
||||
softMin: null,
|
||||
minTimeScale: timeRange.startTime,
|
||||
maxTimeScale: timeRange.endTime,
|
||||
enableZoom: true,
|
||||
}),
|
||||
),
|
||||
[queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime],
|
||||
@ -115,7 +116,7 @@ function Metrics({
|
||||
<div className="metrics-header">
|
||||
<div className="metrics-datetime-section">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showAutoRefresh
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
onTimeChange={handleTimeChange}
|
||||
|
||||
@ -3,6 +3,25 @@
|
||||
background: var(--bg-ink-400);
|
||||
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
|
||||
.log-detail-drawer__title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.log-detail-drawer__title-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.log-detail-drawer__title-right {
|
||||
.ant-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ant-drawer-header {
|
||||
padding: 8px 16px;
|
||||
border-bottom: none;
|
||||
|
||||
@ -8,6 +8,8 @@ import { RadioChangeEvent } from 'antd/lib';
|
||||
import cx from 'classnames';
|
||||
import { LogType } from 'components/Logs/LogStateIndicator/LogStateIndicator';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
|
||||
import InfraMetrics from 'container/LogDetailedView/InfraMetrics/InfraMetrics';
|
||||
import JSONView from 'container/LogDetailedView/JsonView';
|
||||
@ -22,9 +24,12 @@ import dompurify from 'dompurify';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import {
|
||||
BarChart2,
|
||||
Braces,
|
||||
Compass,
|
||||
Copy,
|
||||
Filter,
|
||||
HardHat,
|
||||
@ -33,9 +38,12 @@ import {
|
||||
X,
|
||||
} from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useCopyToClipboard, useLocation } from 'react-use';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
||||
|
||||
import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
|
||||
@ -77,6 +85,12 @@ function LogDetail({
|
||||
});
|
||||
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const location = useLocation();
|
||||
const { safeNavigate } = useSafeNavigate();
|
||||
const urlQuery = useUrlQuery();
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
@ -119,6 +133,21 @@ function LogDetail({
|
||||
});
|
||||
};
|
||||
|
||||
// Go to logs explorer page with the log data
|
||||
const handleOpenInExplorer = (): void => {
|
||||
urlQuery.set(QueryParams.activeLogId, `"${log?.id}"`);
|
||||
urlQuery.set(QueryParams.startTime, minTime?.toString() || '');
|
||||
urlQuery.set(QueryParams.endTime, maxTime?.toString() || '');
|
||||
safeNavigate(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
};
|
||||
|
||||
// Only show when opened from infra monitoring page
|
||||
const showOpenInExplorerBtn = useMemo(
|
||||
() => location.pathname?.includes('/infrastructure-monitoring'),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[],
|
||||
);
|
||||
|
||||
if (!log) {
|
||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
||||
return <></>;
|
||||
@ -131,10 +160,23 @@ function LogDetail({
|
||||
width="60%"
|
||||
maskStyle={{ background: 'none' }}
|
||||
title={
|
||||
<>
|
||||
<div className="log-detail-drawer__title">
|
||||
<div className="log-detail-drawer__title-left">
|
||||
<Divider type="vertical" className={cx('log-type-indicator', LogType)} />
|
||||
<Typography.Text className="title">Log details</Typography.Text>
|
||||
</>
|
||||
</div>
|
||||
{showOpenInExplorerBtn && (
|
||||
<div className="log-detail-drawer__title-right">
|
||||
<Button
|
||||
className="open-in-explorer-btn"
|
||||
icon={<Compass size={16} />}
|
||||
onClick={handleOpenInExplorer}
|
||||
>
|
||||
Open in Explorer
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
placement="right"
|
||||
// closable
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
} from 'mocks-server/__mockdata__/alerts';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import { fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||
import { act, fireEvent, render, screen, waitFor } from 'tests/test-utils';
|
||||
|
||||
import { testLabelInputAndHelpValue } from './testUtils';
|
||||
|
||||
@ -30,6 +30,14 @@ jest.mock('hooks/useNotifications', () => ({
|
||||
},
|
||||
})),
|
||||
}));
|
||||
const showErrorModal = jest.fn();
|
||||
jest.mock('providers/ErrorModalProvider', () => ({
|
||||
__esModule: true,
|
||||
...jest.requireActual('providers/ErrorModalProvider'),
|
||||
useErrorModal: jest.fn(() => ({
|
||||
showErrorModal,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('components/MarkdownRenderer/MarkdownRenderer', () => ({
|
||||
MarkdownRenderer: jest.fn(() => <div>Mocked MarkdownRenderer</div>),
|
||||
@ -119,7 +127,7 @@ describe('Create Alert Channel', () => {
|
||||
|
||||
fireEvent.click(saveButton);
|
||||
|
||||
await waitFor(() => expect(errorNotification).toHaveBeenCalled());
|
||||
await waitFor(() => expect(showErrorModal).toHaveBeenCalled());
|
||||
});
|
||||
it('Should check if clicking on Test button shows "An alert has been sent to this channel" success message if testing passes', async () => {
|
||||
server.use(
|
||||
@ -151,9 +159,11 @@ describe('Create Alert Channel', () => {
|
||||
name: 'button_test_channel',
|
||||
});
|
||||
|
||||
act(() => {
|
||||
fireEvent.click(testButton);
|
||||
});
|
||||
|
||||
await waitFor(() => expect(errorNotification).toHaveBeenCalled());
|
||||
await waitFor(() => expect(showErrorModal).toHaveBeenCalled());
|
||||
});
|
||||
});
|
||||
describe('New Alert Channel Cascading Fields Based on Channel Type', () => {
|
||||
|
||||
@ -16,6 +16,7 @@ import ROUTES from 'constants/routes';
|
||||
import FormAlertChannels from 'container/FormAlertChannels';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import APIError from 'types/api/error';
|
||||
@ -42,6 +43,7 @@ function CreateAlertChannels({
|
||||
}: CreateAlertChannelsProps): JSX.Element {
|
||||
// init namespace for translations
|
||||
const { t } = useTranslation('channels');
|
||||
const { showErrorModal } = useErrorModal();
|
||||
|
||||
const [formInstance] = Form.useForm();
|
||||
|
||||
@ -145,15 +147,12 @@ function CreateAlertChannels({
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).error.error.code,
|
||||
description: (error as APIError).error.error.message,
|
||||
});
|
||||
showErrorModal(error as APIError);
|
||||
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||
} finally {
|
||||
setSavingState(false);
|
||||
}
|
||||
}, [prepareSlackRequest, t, notifications]);
|
||||
}, [prepareSlackRequest, notifications, t, showErrorModal]);
|
||||
|
||||
const prepareWebhookRequest = useCallback(() => {
|
||||
// initial api request without auth params
|
||||
@ -202,15 +201,12 @@ function CreateAlertChannels({
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
showErrorModal(error as APIError);
|
||||
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||
} finally {
|
||||
setSavingState(false);
|
||||
}
|
||||
}, [prepareWebhookRequest, t, notifications]);
|
||||
}, [prepareWebhookRequest, notifications, t, showErrorModal]);
|
||||
|
||||
const preparePagerRequest = useCallback(() => {
|
||||
const validationError = ValidatePagerChannel(selectedConfig as PagerChannel);
|
||||
@ -254,15 +250,12 @@ function CreateAlertChannels({
|
||||
}
|
||||
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
showErrorModal(error as APIError);
|
||||
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||
} finally {
|
||||
setSavingState(false);
|
||||
}
|
||||
}, [t, notifications, preparePagerRequest]);
|
||||
}, [preparePagerRequest, t, notifications, showErrorModal]);
|
||||
|
||||
const prepareOpsgenieRequest = useCallback(
|
||||
() => ({
|
||||
@ -287,15 +280,12 @@ function CreateAlertChannels({
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
showErrorModal(error as APIError);
|
||||
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||
} finally {
|
||||
setSavingState(false);
|
||||
}
|
||||
}, [prepareOpsgenieRequest, t, notifications]);
|
||||
}, [prepareOpsgenieRequest, notifications, t, showErrorModal]);
|
||||
|
||||
const prepareEmailRequest = useCallback(
|
||||
() => ({
|
||||
@ -320,15 +310,12 @@ function CreateAlertChannels({
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
showErrorModal(error as APIError);
|
||||
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||
} finally {
|
||||
setSavingState(false);
|
||||
}
|
||||
}, [prepareEmailRequest, t, notifications]);
|
||||
}, [prepareEmailRequest, notifications, t, showErrorModal]);
|
||||
|
||||
const prepareMsTeamsRequest = useCallback(
|
||||
() => ({
|
||||
@ -353,15 +340,12 @@ function CreateAlertChannels({
|
||||
history.replace(ROUTES.ALL_CHANNELS);
|
||||
return { status: 'success', statusMessage: t('channel_creation_done') };
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).getErrorCode(),
|
||||
description: (error as APIError).getErrorMessage(),
|
||||
});
|
||||
showErrorModal(error as APIError);
|
||||
return { status: 'failed', statusMessage: t('channel_creation_failed') };
|
||||
} finally {
|
||||
setSavingState(false);
|
||||
}
|
||||
}, [prepareMsTeamsRequest, t, notifications]);
|
||||
}, [prepareMsTeamsRequest, notifications, t, showErrorModal]);
|
||||
|
||||
const onSaveHandler = useCallback(
|
||||
async (value: ChannelType) => {
|
||||
@ -459,10 +443,8 @@ function CreateAlertChannels({
|
||||
status: 'Test success',
|
||||
});
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: (error as APIError).error.error.code,
|
||||
description: (error as APIError).error.error.message,
|
||||
});
|
||||
showErrorModal(error as APIError);
|
||||
|
||||
logEvent('Alert Channel: Test notification', {
|
||||
type: channelType,
|
||||
sendResolvedAlert: selectedConfig?.send_resolved,
|
||||
|
||||
@ -14,6 +14,8 @@ function ExplorerOptionWrapper({
|
||||
isLoading,
|
||||
onExport,
|
||||
sourcepage,
|
||||
isOneChartPerQuery,
|
||||
splitedQueries,
|
||||
}: ExplorerOptionsWrapperProps): JSX.Element {
|
||||
const [isExplorerOptionHidden, setIsExplorerOptionHidden] = useState(false);
|
||||
|
||||
@ -32,6 +34,8 @@ function ExplorerOptionWrapper({
|
||||
sourcepage={sourcepage}
|
||||
isExplorerOptionHidden={isExplorerOptionHidden}
|
||||
setIsExplorerOptionHidden={setIsExplorerOptionHidden}
|
||||
isOneChartPerQuery={isOneChartPerQuery}
|
||||
splitedQueries={splitedQueries}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -8,6 +8,21 @@
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
background-color: transparent;
|
||||
|
||||
.multi-alert-button,
|
||||
.multi-dashboard-button {
|
||||
min-width: 130px;
|
||||
|
||||
.ant-select-selector {
|
||||
.ant-select-selection-placeholder {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hide-update {
|
||||
|
||||
@ -90,6 +90,8 @@ function ExplorerOptions({
|
||||
sourcepage,
|
||||
isExplorerOptionHidden = false,
|
||||
setIsExplorerOptionHidden,
|
||||
isOneChartPerQuery = false,
|
||||
splitedQueries = [],
|
||||
}: ExplorerOptionsProps): JSX.Element {
|
||||
const [isExport, setIsExport] = useState<boolean>(false);
|
||||
const [isSaveModalOpen, setIsSaveModalOpen] = useState(false);
|
||||
@ -99,6 +101,8 @@ function ExplorerOptions({
|
||||
const history = useHistory();
|
||||
const ref = useRef<RefSelectProps>(null);
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const [queryToExport, setQueryToExport] = useState<Query | null>(null);
|
||||
|
||||
const isLogsExplorer = sourcepage === DataSource.LOGS;
|
||||
const isMetricsExplorer = sourcepage === DataSource.METRICS;
|
||||
|
||||
@ -149,22 +153,28 @@ function ExplorerOptions({
|
||||
|
||||
const { user } = useAppContext();
|
||||
|
||||
const handleConditionalQueryModification = useCallback((): string => {
|
||||
const handleConditionalQueryModification = useCallback(
|
||||
(defaultQuery: Query | null): string => {
|
||||
const queryToUse = defaultQuery || query;
|
||||
if (
|
||||
query?.builder?.queryData?.[0]?.aggregateOperator !== StringOperators.NOOP
|
||||
queryToUse?.builder?.queryData?.[0]?.aggregateOperator !==
|
||||
StringOperators.NOOP
|
||||
) {
|
||||
return JSON.stringify(query);
|
||||
return JSON.stringify(queryToUse);
|
||||
}
|
||||
|
||||
// Modify aggregateOperator to count, as noop is not supported in alerts
|
||||
const modifiedQuery = cloneDeep(query);
|
||||
const modifiedQuery = cloneDeep(queryToUse);
|
||||
|
||||
modifiedQuery.builder.queryData[0].aggregateOperator = StringOperators.COUNT;
|
||||
|
||||
return JSON.stringify(modifiedQuery);
|
||||
}, [query]);
|
||||
},
|
||||
[query],
|
||||
);
|
||||
|
||||
const onCreateAlertsHandler = useCallback(() => {
|
||||
const onCreateAlertsHandler = useCallback(
|
||||
(defaultQuery: Query | null) => {
|
||||
if (sourcepage === DataSource.TRACES) {
|
||||
logEvent('Traces Explorer: Create alert', {
|
||||
panelType,
|
||||
@ -179,21 +189,26 @@ function ExplorerOptions({
|
||||
});
|
||||
}
|
||||
|
||||
const stringifiedQuery = handleConditionalQueryModification();
|
||||
const stringifiedQuery = handleConditionalQueryModification(defaultQuery);
|
||||
|
||||
history.push(
|
||||
`${ROUTES.ALERTS_NEW}?${QueryParams.compositeQuery}=${encodeURIComponent(
|
||||
stringifiedQuery,
|
||||
)}`,
|
||||
);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [handleConditionalQueryModification, history]);
|
||||
[handleConditionalQueryModification, history],
|
||||
);
|
||||
|
||||
const onCancel = (value: boolean) => (): void => {
|
||||
onModalToggle(value);
|
||||
if (isOneChartPerQuery) {
|
||||
setQueryToExport(null);
|
||||
}
|
||||
};
|
||||
|
||||
const onAddToDashboard = (): void => {
|
||||
const onAddToDashboard = useCallback((): void => {
|
||||
if (sourcepage === DataSource.TRACES) {
|
||||
logEvent('Traces Explorer: Add to dashboard clicked', {
|
||||
panelType,
|
||||
@ -208,7 +223,7 @@ function ExplorerOptions({
|
||||
});
|
||||
}
|
||||
setIsExport(true);
|
||||
};
|
||||
}, [isLogsExplorer, isMetricsExplorer, panelType, setIsExport, sourcepage]);
|
||||
|
||||
const {
|
||||
data: viewsData,
|
||||
@ -616,6 +631,120 @@ function ExplorerOptions({
|
||||
return 'https://signoz.io/docs/product-features/trace-explorer/?utm_source=product&utm_medium=trace-explorer-toolbar';
|
||||
}, [isLogsExplorer, isMetricsExplorer]);
|
||||
|
||||
const getQueryName = (query: Query): string => {
|
||||
if (query.builder.queryFormulas.length > 0) {
|
||||
return `Formula ${query.builder.queryFormulas[0].queryName}`;
|
||||
}
|
||||
return `Query ${query.builder.queryData[0].queryName}`;
|
||||
};
|
||||
|
||||
const alertButton = useMemo(() => {
|
||||
if (isOneChartPerQuery) {
|
||||
const selectLabel = (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
shape="round"
|
||||
icon={<ConciergeBell size={16} />}
|
||||
>
|
||||
Create an Alert
|
||||
</Button>
|
||||
);
|
||||
return (
|
||||
<Select
|
||||
disabled={disabled}
|
||||
className="multi-alert-button"
|
||||
placeholder={selectLabel}
|
||||
value={selectLabel}
|
||||
suffixIcon={null}
|
||||
onSelect={(e): void => {
|
||||
const selectedQuery = splitedQueries.find(
|
||||
(query) => query.id === ((e as unknown) as string),
|
||||
);
|
||||
if (selectedQuery) {
|
||||
onCreateAlertsHandler(selectedQuery);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{splitedQueries.map((splittedQuery) => (
|
||||
<Select.Option key={splittedQuery.id} value={splittedQuery.id}>
|
||||
{getQueryName(splittedQuery)}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
disabled={disabled}
|
||||
shape="round"
|
||||
onClick={(): void => onCreateAlertsHandler(query)}
|
||||
icon={<ConciergeBell size={16} />}
|
||||
>
|
||||
Create an Alert
|
||||
</Button>
|
||||
);
|
||||
}, [
|
||||
disabled,
|
||||
isOneChartPerQuery,
|
||||
onCreateAlertsHandler,
|
||||
query,
|
||||
splitedQueries,
|
||||
]);
|
||||
|
||||
const dashboardButton = useMemo(() => {
|
||||
if (isOneChartPerQuery) {
|
||||
const selectLabel = (
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={disabled}
|
||||
shape="round"
|
||||
onClick={onAddToDashboard}
|
||||
icon={<Plus size={16} />}
|
||||
>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
);
|
||||
return (
|
||||
<Select
|
||||
disabled={disabled}
|
||||
className="multi-dashboard-button"
|
||||
placeholder={selectLabel}
|
||||
value={selectLabel}
|
||||
suffixIcon={null}
|
||||
onSelect={(e): void => {
|
||||
const selectedQuery = splitedQueries.find(
|
||||
(query) => query.id === ((e as unknown) as string),
|
||||
);
|
||||
if (selectedQuery) {
|
||||
setQueryToExport(() => {
|
||||
onAddToDashboard();
|
||||
return selectedQuery;
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* eslint-disable-next-line sonarjs/no-identical-functions */}
|
||||
{splitedQueries.map((splittedQuery) => (
|
||||
<Select.Option key={splittedQuery.id} value={splittedQuery.id}>
|
||||
{getQueryName(splittedQuery)}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={disabled}
|
||||
shape="round"
|
||||
onClick={onAddToDashboard}
|
||||
icon={<Plus size={16} />}
|
||||
>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
);
|
||||
}, [disabled, isOneChartPerQuery, onAddToDashboard, splitedQueries]);
|
||||
|
||||
return (
|
||||
<div className="explorer-options-container">
|
||||
{
|
||||
@ -719,24 +848,8 @@ function ExplorerOptions({
|
||||
<hr className={isEditDeleteSupported ? '' : 'hidden'} />
|
||||
|
||||
<div className={cx('actions', isEditDeleteSupported ? '' : 'hidden')}>
|
||||
<Button
|
||||
disabled={disabled}
|
||||
shape="round"
|
||||
onClick={onCreateAlertsHandler}
|
||||
icon={<ConciergeBell size={16} />}
|
||||
>
|
||||
Create an Alert
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={disabled}
|
||||
shape="round"
|
||||
onClick={onAddToDashboard}
|
||||
icon={<Plus size={16} />}
|
||||
>
|
||||
Add to Dashboard
|
||||
</Button>
|
||||
{alertButton}
|
||||
{dashboardButton}
|
||||
</div>
|
||||
<div className="actions">
|
||||
{/* Hide the info icon for metrics explorer until we get the docs link */}
|
||||
@ -818,9 +931,15 @@ function ExplorerOptions({
|
||||
destroyOnClose
|
||||
>
|
||||
<ExportPanelContainer
|
||||
query={query}
|
||||
query={isOneChartPerQuery ? queryToExport : query}
|
||||
isLoading={isLoading}
|
||||
onExport={onExport}
|
||||
onExport={(dashboard, isNewDashboard): void => {
|
||||
if (isOneChartPerQuery && queryToExport) {
|
||||
onExport(dashboard, isNewDashboard, queryToExport);
|
||||
} else {
|
||||
onExport(dashboard, isNewDashboard);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
@ -829,18 +948,26 @@ function ExplorerOptions({
|
||||
|
||||
export interface ExplorerOptionsProps {
|
||||
isLoading?: boolean;
|
||||
onExport: (dashboard: Dashboard | null, isNewDashboard?: boolean) => void;
|
||||
onExport: (
|
||||
dashboard: Dashboard | null,
|
||||
isNewDashboard?: boolean,
|
||||
queryToExport?: Query,
|
||||
) => void;
|
||||
query: Query | null;
|
||||
disabled: boolean;
|
||||
sourcepage: DataSource;
|
||||
isExplorerOptionHidden?: boolean;
|
||||
setIsExplorerOptionHidden?: Dispatch<SetStateAction<boolean>>;
|
||||
isOneChartPerQuery?: boolean;
|
||||
splitedQueries?: Query[];
|
||||
}
|
||||
|
||||
ExplorerOptions.defaultProps = {
|
||||
isLoading: false,
|
||||
isExplorerOptionHidden: false,
|
||||
setIsExplorerOptionHidden: undefined,
|
||||
isOneChartPerQuery: false,
|
||||
splitedQueries: [],
|
||||
};
|
||||
|
||||
export default ExplorerOptions;
|
||||
|
||||
@ -142,6 +142,7 @@ function ChartPreview({
|
||||
params: {
|
||||
allowSelectedIntervalForStepGen,
|
||||
},
|
||||
originalGraphType: graphType,
|
||||
},
|
||||
alertDef?.version || DEFAULT_ENTITY_VERSION,
|
||||
{
|
||||
|
||||
@ -94,6 +94,7 @@ function FullView({
|
||||
variables: getDashboardVariables(selectedDashboard?.data.variables),
|
||||
fillGaps: widget.fillSpans,
|
||||
formatForWeb: widget.panelTypes === PANEL_TYPES.TABLE,
|
||||
originalGraphType: widget?.panelTypes,
|
||||
};
|
||||
}
|
||||
updatedQuery.builder.queryData[0].pageSize = 10;
|
||||
|
||||
@ -208,6 +208,7 @@ function GridCardGraph({
|
||||
: globalSelectedInterval,
|
||||
start: customTimeRange?.startTime || start,
|
||||
end: customTimeRange?.endTime || end,
|
||||
originalGraphType: widget?.panelTypes,
|
||||
},
|
||||
version || DEFAULT_ENTITY_VERSION,
|
||||
{
|
||||
|
||||
228
frontend/src/container/GridCardLayout/__tests__/utils.test.ts
Normal file
228
frontend/src/container/GridCardLayout/__tests__/utils.test.ts
Normal file
@ -0,0 +1,228 @@
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { getStepIntervalPoints, updateStepInterval } from '../utils';
|
||||
|
||||
describe('GridCardLayout Utils', () => {
|
||||
describe('getStepIntervalPoints', () => {
|
||||
it('should return 60 points for duration <= 1 hour', () => {
|
||||
// 30 minutes in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 30 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(60);
|
||||
});
|
||||
|
||||
it('should return 60 points for exactly 1 hour', () => {
|
||||
// 1 hour in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(60);
|
||||
});
|
||||
|
||||
it('should return 120 points for duration <= 3 hours', () => {
|
||||
// 2 hours in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 2 * 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(120);
|
||||
});
|
||||
|
||||
it('should return 120 points for exactly 3 hours', () => {
|
||||
// 3 hours in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 3 * 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(120);
|
||||
});
|
||||
|
||||
it('should return 180 points for duration <= 5 hours', () => {
|
||||
// 4 hours in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 4 * 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(180);
|
||||
});
|
||||
|
||||
it('should return 180 points for exactly 5 hours', () => {
|
||||
// 5 hours in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 5 * 60 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(180);
|
||||
});
|
||||
|
||||
it('should calculate dynamic interval for duration > 5 hours', () => {
|
||||
// 10 hours in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 10 * 60 * 60 * 1000;
|
||||
|
||||
const result = getStepIntervalPoints(start, end);
|
||||
|
||||
// For 10 hours (600 minutes), interval should be ceil(600/80) = 8, rounded to 10, then * 60 = 600
|
||||
expect(result).toBe(600);
|
||||
});
|
||||
|
||||
it('should handle very long durations correctly', () => {
|
||||
// 7 days in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 7 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const result = getStepIntervalPoints(start, end);
|
||||
|
||||
// For 7 days (10080 minutes), interval should be ceil(10080/80) = 126, rounded to 130, then * 60 = 7800
|
||||
expect(result).toBe(7800);
|
||||
});
|
||||
|
||||
it('should round up to nearest multiple of 5 minutes', () => {
|
||||
// 12 hours in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 12 * 60 * 60 * 1000;
|
||||
|
||||
const result = getStepIntervalPoints(start, end);
|
||||
|
||||
// For 12 hours (720 minutes), interval should be ceil(720/80) = 9, rounded to 10, then * 60 = 600
|
||||
expect(result).toBe(600);
|
||||
});
|
||||
|
||||
it('should handle edge case with very small duration', () => {
|
||||
// 1 minute in milliseconds
|
||||
const start = Date.now();
|
||||
const end = start + 1 * 60 * 1000;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(60);
|
||||
});
|
||||
|
||||
it('should handle zero duration', () => {
|
||||
const start = Date.now();
|
||||
const end = start;
|
||||
|
||||
expect(getStepIntervalPoints(start, end)).toBe(60);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateStepInterval', () => {
|
||||
const mockQuery: Query = {
|
||||
queryType: EQueryType.QUERY_BUILDER,
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
stepInterval: 30,
|
||||
aggregateOperator: 'avg',
|
||||
dataSource: DataSource.METRICS,
|
||||
queryName: 'A',
|
||||
aggregateAttribute: { key: 'cpu_usage', type: 'Gauge' },
|
||||
timeAggregation: 'avg',
|
||||
spaceAggregation: 'avg',
|
||||
functions: [],
|
||||
filters: { items: [], op: 'AND' },
|
||||
expression: 'A',
|
||||
disabled: false,
|
||||
having: [],
|
||||
groupBy: [],
|
||||
orderBy: [],
|
||||
limit: null,
|
||||
offset: 0,
|
||||
pageSize: 0,
|
||||
reduceTo: 'avg',
|
||||
legend: '',
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
clickhouse_sql: [],
|
||||
promql: [],
|
||||
id: 'test-query',
|
||||
};
|
||||
|
||||
it('should update stepInterval based on time range', () => {
|
||||
// 2 hours duration
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 2 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateStepInterval(mockQuery, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(120);
|
||||
});
|
||||
|
||||
it('should preserve other query properties', () => {
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 1 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateStepInterval(mockQuery, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData[0].aggregateOperator).toBe('avg');
|
||||
expect(result.builder.queryData[0].queryName).toBe('A');
|
||||
expect(result.builder.queryData[0].dataSource).toBe('metrics');
|
||||
});
|
||||
|
||||
it('should handle multiple queryData items', () => {
|
||||
const multiQueryMock: Query = {
|
||||
...mockQuery,
|
||||
builder: {
|
||||
queryData: [
|
||||
...mockQuery.builder.queryData,
|
||||
{
|
||||
...mockQuery.builder.queryData[0],
|
||||
queryName: 'B',
|
||||
stepInterval: 45,
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
};
|
||||
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 4 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateStepInterval(multiQueryMock, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData).toHaveLength(2);
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(180);
|
||||
expect(result.builder.queryData[1].stepInterval).toBe(180);
|
||||
});
|
||||
|
||||
it('should use calculated stepInterval when original is undefined', () => {
|
||||
const queryWithUndefinedStep: Query = {
|
||||
...mockQuery,
|
||||
builder: {
|
||||
queryData: [
|
||||
{
|
||||
...mockQuery.builder.queryData[0],
|
||||
stepInterval: undefined as any,
|
||||
},
|
||||
],
|
||||
queryFormulas: [],
|
||||
},
|
||||
};
|
||||
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 1 * 60 * 60 * 1000;
|
||||
|
||||
const result = updateStepInterval(queryWithUndefinedStep, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(60);
|
||||
});
|
||||
|
||||
it('should fallback to 60 when calculated stepInterval is 0', () => {
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime; // Same time = 0 duration
|
||||
|
||||
const result = updateStepInterval(mockQuery, minTime, maxTime);
|
||||
|
||||
expect(result.builder.queryData[0].stepInterval).toBe(60);
|
||||
});
|
||||
|
||||
it('should handle very large time ranges', () => {
|
||||
const minTime = Date.now();
|
||||
const maxTime = minTime + 30 * 24 * 60 * 60 * 1000; // 30 days
|
||||
|
||||
const result = updateStepInterval(mockQuery, minTime, maxTime);
|
||||
|
||||
// Should calculate appropriate interval for 30 days
|
||||
expect(result.builder.queryData[0].stepInterval).toBeGreaterThan(180);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -2,6 +2,7 @@ import { FORMULA_REGEXP } from 'constants/regExp';
|
||||
import { isEmpty, isEqual } from 'lodash-es';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import { Dashboard, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
export const removeUndefinedValuesFromLayout = (layout: Layout[]): Layout[] =>
|
||||
layout.map((obj) =>
|
||||
@ -51,3 +52,63 @@ export const hasColumnWidthsChanged = (
|
||||
return !isEqual(newWidths, existingWidths);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates the step interval in uPlot points (1 minute = 60 points)
|
||||
* based on the time duration between two timestamps in nanoseconds.
|
||||
*
|
||||
* Conversion logic:
|
||||
* - <= 1 hr → 1 min (60 points)
|
||||
* - <= 3 hr → 2 min (120 points)
|
||||
* - <= 5 hr → 3 min (180 points)
|
||||
* - > 5 hr → max 80 bars, ceil((end-start)/80), rounded to nearest multiple of 5 min
|
||||
*
|
||||
* @param startNano - start time in nanoseconds
|
||||
* @param endNano - end time in nanoseconds
|
||||
* @returns stepInterval in uPlot points
|
||||
*/
|
||||
export function getStepIntervalPoints(
|
||||
startNano: number,
|
||||
endNano: number,
|
||||
): number {
|
||||
const startMs = startNano;
|
||||
const endMs = endNano;
|
||||
const durationMs = endMs - startMs;
|
||||
const durationMin = durationMs / (60 * 1000); // convert to minutes
|
||||
|
||||
if (durationMin <= 60) {
|
||||
return 60; // 1 min
|
||||
}
|
||||
if (durationMin <= 180) {
|
||||
return 120; // 2 min
|
||||
}
|
||||
if (durationMin <= 300) {
|
||||
return 180; // 3 min
|
||||
}
|
||||
|
||||
const totalPoints = Math.ceil(durationMs / (1000 * 60)); // total minutes
|
||||
const interval = Math.ceil(totalPoints / 80); // at most 80 bars
|
||||
const roundedInterval = Math.ceil(interval / 5) * 5; // round up to nearest 5
|
||||
return roundedInterval * 60; // convert min to points
|
||||
}
|
||||
|
||||
export function updateStepInterval(
|
||||
query: Query,
|
||||
minTime: number,
|
||||
maxTime: number,
|
||||
): Query {
|
||||
const stepIntervalPoints = getStepIntervalPoints(minTime, maxTime);
|
||||
|
||||
return {
|
||||
...query,
|
||||
builder: {
|
||||
...query.builder,
|
||||
queryData: [
|
||||
...query.builder.queryData.map((queryData) => ({
|
||||
...queryData,
|
||||
stepInterval: stepIntervalPoints || queryData.stepInterval || 60,
|
||||
})),
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -8,6 +8,11 @@ import HostMetricDetail from 'components/HostMetricsDetail';
|
||||
import QuickFilters from 'components/QuickFilters/QuickFilters';
|
||||
import { QuickFiltersSource } from 'components/QuickFilters/types';
|
||||
import { InfraMonitoringEvents } from 'constants/events';
|
||||
import {
|
||||
getFiltersFromParams,
|
||||
getOrderByFromParams,
|
||||
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { INFRA_MONITORING_K8S_PARAMS_KEYS } from 'container/InfraMonitoringK8s/constants';
|
||||
import { usePageSize } from 'container/InfraMonitoringK8s/utils';
|
||||
import { useGetHostList } from 'hooks/infraMonitoring/useGetHostList';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
@ -15,6 +20,7 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { Filter } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
@ -27,20 +33,51 @@ function HostsList(): JSX.Element {
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [filters, setFilters] = useState<IBuilderQuery['filters']>({
|
||||
const [filters, setFilters] = useState<IBuilderQuery['filters']>(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS,
|
||||
);
|
||||
if (!filters) {
|
||||
return {
|
||||
items: [],
|
||||
op: 'and',
|
||||
};
|
||||
}
|
||||
return filters;
|
||||
});
|
||||
const [showFilters, setShowFilters] = useState<boolean>(true);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(null);
|
||||
} | null>(() => getOrderByFromParams(searchParams));
|
||||
|
||||
const [selectedHostName, setSelectedHostName] = useState<string | null>(null);
|
||||
const handleOrderByChange = (
|
||||
orderBy: {
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null,
|
||||
): void => {
|
||||
setOrderBy(orderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(orderBy),
|
||||
});
|
||||
};
|
||||
|
||||
const [selectedHostName, setSelectedHostName] = useState<string | null>(() => {
|
||||
const hostName = searchParams.get('hostName');
|
||||
return hostName || null;
|
||||
});
|
||||
|
||||
const handleHostClick = (hostName: string): void => {
|
||||
setSelectedHostName(hostName);
|
||||
setSearchParams({ ...searchParams, hostName });
|
||||
};
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize('hosts');
|
||||
|
||||
@ -82,6 +119,10 @@ function HostsList(): JSX.Element {
|
||||
const isNewFilterAdded = value.items.length !== filters.items.length;
|
||||
setFilters(value);
|
||||
handleChangeQueryData('filters', value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS]: JSON.stringify(value),
|
||||
});
|
||||
if (isNewFilterAdded) {
|
||||
setCurrentPage(1);
|
||||
|
||||
@ -161,7 +202,10 @@ function HostsList(): JSX.Element {
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
<HostsListControls handleFiltersChange={handleFiltersChange} />
|
||||
<HostsListControls
|
||||
filters={filters}
|
||||
handleFiltersChange={handleFiltersChange}
|
||||
/>
|
||||
</div>
|
||||
<HostsListTable
|
||||
isLoading={isLoading}
|
||||
@ -172,10 +216,10 @@ function HostsList(): JSX.Element {
|
||||
filters={filters}
|
||||
currentPage={currentPage}
|
||||
setCurrentPage={setCurrentPage}
|
||||
setSelectedHostName={setSelectedHostName}
|
||||
onHostClick={handleHostClick}
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
setOrderBy={setOrderBy}
|
||||
setOrderBy={handleOrderByChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -10,8 +10,10 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
function HostsListControls({
|
||||
handleFiltersChange,
|
||||
filters,
|
||||
}: {
|
||||
handleFiltersChange: (value: IBuilderQuery['filters']) => void;
|
||||
filters: IBuilderQuery['filters'];
|
||||
}): JSX.Element {
|
||||
const currentQuery = initialQueriesMap[DataSource.METRICS];
|
||||
const updatedCurrentQuery = useMemo(
|
||||
@ -26,11 +28,12 @@ function HostsListControls({
|
||||
aggregateAttribute: {
|
||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||
},
|
||||
filters,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[currentQuery],
|
||||
[currentQuery, filters],
|
||||
);
|
||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ export default function HostsListTable({
|
||||
tableData: data,
|
||||
hostMetricsData,
|
||||
filters,
|
||||
setSelectedHostName,
|
||||
onHostClick,
|
||||
currentPage,
|
||||
setCurrentPage,
|
||||
pageSize,
|
||||
@ -77,7 +77,7 @@ export default function HostsListTable({
|
||||
);
|
||||
|
||||
const handleRowClick = (record: HostRowData): void => {
|
||||
setSelectedHostName(record.hostName);
|
||||
onHostClick(record.hostName);
|
||||
logEvent(InfraMonitoringEvents.ItemClicked, {
|
||||
entity: InfraMonitoringEvents.HostEntity,
|
||||
page: InfraMonitoringEvents.ListPage,
|
||||
|
||||
@ -41,16 +41,13 @@ export interface HostsListTableProps {
|
||||
| undefined;
|
||||
hostMetricsData: HostData[];
|
||||
filters: TagFilter;
|
||||
setSelectedHostName: Dispatch<SetStateAction<string | null>>;
|
||||
onHostClick: (hostName: string) => void;
|
||||
currentPage: number;
|
||||
setCurrentPage: Dispatch<SetStateAction<number>>;
|
||||
pageSize: number;
|
||||
setOrderBy: Dispatch<
|
||||
SetStateAction<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>
|
||||
>;
|
||||
setOrderBy: (
|
||||
orderBy: { columnName: string; order: 'asc' | 'desc' } | null,
|
||||
) => void;
|
||||
setPageSize: (pageSize: number) => void;
|
||||
}
|
||||
|
||||
|
||||
@ -14,8 +14,14 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import {
|
||||
filterDuplicateFilters,
|
||||
getFiltersFromParams,
|
||||
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||
import {
|
||||
CustomTimeType,
|
||||
@ -34,6 +40,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@ -82,11 +89,27 @@ function ClusterDetails({
|
||||
selectedTime as Time,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(() => {
|
||||
const view = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
if (view) {
|
||||
return view as VIEWS;
|
||||
}
|
||||
return VIEWS.METRICS;
|
||||
});
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => ({
|
||||
const initialFilters = useMemo(() => {
|
||||
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
const queryKey =
|
||||
urlView === VIEW_TYPES.LOGS
|
||||
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -103,12 +126,18 @@ function ClusterDetails({
|
||||
value: cluster?.meta.k8s_cluster_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[cluster?.meta.k8s_cluster_name],
|
||||
);
|
||||
};
|
||||
}, [cluster?.meta.k8s_cluster_name, searchParams]);
|
||||
|
||||
const initialEventsFilters = useMemo(
|
||||
() => ({
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -138,9 +167,8 @@ function ClusterDetails({
|
||||
value: cluster?.meta.k8s_cluster_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[cluster?.meta.k8s_cluster_name],
|
||||
);
|
||||
};
|
||||
}, [cluster?.meta.k8s_cluster_name, searchParams]);
|
||||
|
||||
const [logsAndTracesFilters, setLogsAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@ -181,6 +209,13 @@ function ClusterDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(null),
|
||||
});
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@ -220,7 +255,7 @@ function ClusterDetails({
|
||||
);
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogsAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
|
||||
@ -240,7 +275,7 @@ function ClusterDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -250,6 +285,16 @@ function ClusterDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -257,7 +302,7 @@ function ClusterDetails({
|
||||
);
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogsAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_CLUSTER_NAME].includes(item.key?.key ?? ''),
|
||||
@ -272,7 +317,7 @@ function ClusterDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -283,6 +328,16 @@ function ClusterDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -290,7 +345,7 @@ function ClusterDetails({
|
||||
);
|
||||
|
||||
const handleChangeEventsFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setEventsFilters((prevFilters) => {
|
||||
const clusterKindFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
@ -308,7 +363,7 @@ function ClusterDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -322,6 +377,16 @@ function ClusterDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -23,11 +23,14 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
@ -59,19 +62,36 @@ function K8sClustersList({
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>({ columnName: 'cpu', order: 'desc' });
|
||||
} | null>(() => getOrderByFromParams(searchParams, false));
|
||||
|
||||
const [selectedClusterName, setselectedClusterName] = useState<string | null>(
|
||||
null,
|
||||
() => {
|
||||
const clusterName = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.CLUSTER_NAME,
|
||||
);
|
||||
if (clusterName) {
|
||||
return clusterName;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.CLUSTERS);
|
||||
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||
if (groupBy) {
|
||||
const decoded = decodeURIComponent(groupBy);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['groupBy'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@ -258,15 +278,26 @@ function K8sClustersList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
setOrderBy({
|
||||
const currentOrderBy = {
|
||||
columnName: sorter.field as string,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||
order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc',
|
||||
};
|
||||
setOrderBy(currentOrderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(
|
||||
currentOrderBy,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -322,6 +353,10 @@ function K8sClustersList({
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setselectedClusterName(record.clusterUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CLUSTER_NAME]: record.clusterUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@ -348,6 +383,11 @@ function K8sClustersList({
|
||||
setSelectedRowData(null);
|
||||
setGroupBy([]);
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
};
|
||||
|
||||
const expandedRowRender = (): JSX.Element => (
|
||||
@ -372,7 +412,9 @@ function K8sClustersList({
|
||||
}}
|
||||
showHeader={false}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => setselectedClusterName(record.clusterUID),
|
||||
onClick: (): void => {
|
||||
setselectedClusterName(record.clusterUID);
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
/>
|
||||
@ -436,6 +478,20 @@ function K8sClustersList({
|
||||
|
||||
const handleCloseClusterDetail = (): void => {
|
||||
setselectedClusterName(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(
|
||||
Array.from(searchParams.entries()).filter(
|
||||
([key]) =>
|
||||
![
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.CLUSTER_NAME,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS,
|
||||
].includes(key),
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@ -457,6 +513,10 @@ function K8sClustersList({
|
||||
// Reset pagination on switching to groupBy
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
setExpandedRowKeys([]);
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@ -464,7 +524,7 @@ function K8sClustersList({
|
||||
category: InfraMonitoringEvents.Cluster,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -13,7 +13,11 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@ -31,6 +35,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@ -83,11 +88,27 @@ function DaemonSetDetails({
|
||||
selectedTime as Time,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(() => {
|
||||
const view = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
if (view) {
|
||||
return view as VIEWS;
|
||||
}
|
||||
return VIEWS.METRICS;
|
||||
});
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => ({
|
||||
const initialFilters = useMemo(() => {
|
||||
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
const queryKey =
|
||||
urlView === VIEW_TYPES.LOGS
|
||||
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -117,12 +138,22 @@ function DaemonSetDetails({
|
||||
value: daemonSet?.meta.k8s_namespace_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[daemonSet?.meta.k8s_daemonset_name, daemonSet?.meta.k8s_namespace_name],
|
||||
);
|
||||
};
|
||||
}, [
|
||||
daemonSet?.meta.k8s_daemonset_name,
|
||||
daemonSet?.meta.k8s_namespace_name,
|
||||
searchParams,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(
|
||||
() => ({
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -152,9 +183,8 @@ function DaemonSetDetails({
|
||||
value: daemonSet?.meta.k8s_daemonset_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[daemonSet?.meta.k8s_daemonset_name],
|
||||
);
|
||||
};
|
||||
}, [daemonSet?.meta.k8s_daemonset_name, searchParams]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@ -195,6 +225,13 @@ function DaemonSetDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(null),
|
||||
});
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@ -234,7 +271,7 @@ function DaemonSetDetails({
|
||||
);
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
@ -257,7 +294,7 @@ function DaemonSetDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
@ -265,6 +302,15 @@ function DaemonSetDetails({
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -272,7 +318,7 @@ function DaemonSetDetails({
|
||||
);
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_DAEMON_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
@ -289,7 +335,7 @@ function DaemonSetDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
@ -298,6 +344,16 @@ function DaemonSetDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -305,7 +361,7 @@ function DaemonSetDetails({
|
||||
);
|
||||
|
||||
const handleChangeEventsFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setEventsFilters((prevFilters) => {
|
||||
const daemonSetKindFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
@ -323,7 +379,7 @@ function DaemonSetDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
daemonSetKindFilter,
|
||||
@ -335,6 +391,16 @@ function DaemonSetDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -24,11 +24,14 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
@ -59,21 +62,38 @@ function K8sDaemonSetsList({
|
||||
);
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(null);
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
|
||||
const [selectedDaemonSetUID, setselectedDaemonSetUID] = useState<
|
||||
const [selectedDaemonSetUID, setSelectedDaemonSetUID] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
>(() => {
|
||||
const daemonSetUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.DAEMONSET_UID,
|
||||
);
|
||||
if (daemonSetUID) {
|
||||
return daemonSetUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.DAEMONSETS);
|
||||
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||
if (groupBy) {
|
||||
const decoded = decodeURIComponent(groupBy);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['groupBy'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@ -262,15 +282,26 @@ function K8sDaemonSetsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
setOrderBy({
|
||||
const currentOrderBy = {
|
||||
columnName: sorter.field as string,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||
order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc',
|
||||
};
|
||||
setOrderBy(currentOrderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(
|
||||
currentOrderBy,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -329,7 +360,11 @@ function K8sDaemonSetsList({
|
||||
const handleRowClick = (record: K8sDaemonSetsRowData): void => {
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setselectedDaemonSetUID(record.daemonsetUID);
|
||||
setSelectedDaemonSetUID(record.daemonsetUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.DAEMONSET_UID]: record.daemonsetUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@ -356,6 +391,11 @@ function K8sDaemonSetsList({
|
||||
setSelectedRowData(null);
|
||||
setGroupBy([]);
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
};
|
||||
|
||||
const expandedRowRender = (): JSX.Element => (
|
||||
@ -380,7 +420,9 @@ function K8sDaemonSetsList({
|
||||
}}
|
||||
showHeader={false}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => setselectedDaemonSetUID(record.daemonsetUID),
|
||||
onClick: (): void => {
|
||||
setSelectedDaemonSetUID(record.daemonsetUID);
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
/>
|
||||
@ -443,7 +485,21 @@ function K8sDaemonSetsList({
|
||||
};
|
||||
|
||||
const handleCloseDaemonSetDetail = (): void => {
|
||||
setselectedDaemonSetUID(null);
|
||||
setSelectedDaemonSetUID(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(
|
||||
Array.from(searchParams.entries()).filter(
|
||||
([key]) =>
|
||||
![
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.DAEMONSET_UID,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS,
|
||||
].includes(key),
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@ -464,6 +520,10 @@ function K8sDaemonSetsList({
|
||||
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
@ -472,7 +532,7 @@ function K8sDaemonSetsList({
|
||||
category: InfraMonitoringEvents.DaemonSet,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -14,8 +14,14 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import {
|
||||
filterDuplicateFilters,
|
||||
getFiltersFromParams,
|
||||
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||
import {
|
||||
CustomTimeType,
|
||||
@ -34,6 +40,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@ -85,11 +92,27 @@ function DeploymentDetails({
|
||||
selectedTime as Time,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(() => {
|
||||
const view = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
if (view) {
|
||||
return view as VIEWS;
|
||||
}
|
||||
return VIEWS.METRICS;
|
||||
});
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => ({
|
||||
const initialFilters = useMemo(() => {
|
||||
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
const queryKey =
|
||||
urlView === VIEW_TYPES.LOGS
|
||||
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -119,12 +142,22 @@ function DeploymentDetails({
|
||||
value: deployment?.meta.k8s_namespace_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[deployment?.meta.k8s_deployment_name, deployment?.meta.k8s_namespace_name],
|
||||
);
|
||||
};
|
||||
}, [
|
||||
deployment?.meta.k8s_deployment_name,
|
||||
deployment?.meta.k8s_namespace_name,
|
||||
searchParams,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(
|
||||
() => ({
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -154,9 +187,8 @@ function DeploymentDetails({
|
||||
value: deployment?.meta.k8s_deployment_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[deployment?.meta.k8s_deployment_name],
|
||||
);
|
||||
};
|
||||
}, [deployment?.meta.k8s_deployment_name, searchParams]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@ -197,6 +229,13 @@ function DeploymentDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(null),
|
||||
});
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@ -236,7 +275,7 @@ function DeploymentDetails({
|
||||
);
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
@ -259,7 +298,7 @@ function DeploymentDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -269,6 +308,16 @@ function DeploymentDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -276,7 +325,7 @@ function DeploymentDetails({
|
||||
);
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_DEPLOYMENT_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
@ -293,7 +342,7 @@ function DeploymentDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -304,6 +353,16 @@ function DeploymentDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -311,7 +370,7 @@ function DeploymentDetails({
|
||||
);
|
||||
|
||||
const handleChangeEventsFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setEventsFilters((prevFilters) => {
|
||||
const deploymentKindFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
@ -329,7 +388,7 @@ function DeploymentDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -343,6 +402,16 @@ function DeploymentDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -24,11 +24,14 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
@ -61,19 +64,36 @@ function K8sDeploymentsList({
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(null);
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
|
||||
const [selectedDeploymentUID, setselectedDeploymentUID] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
>(() => {
|
||||
const deploymentUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.DEPLOYMENT_UID,
|
||||
);
|
||||
if (deploymentUID) {
|
||||
return deploymentUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.DEPLOYMENTS);
|
||||
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||
if (groupBy) {
|
||||
const decoded = decodeURIComponent(groupBy);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['groupBy'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@ -264,15 +284,26 @@ function K8sDeploymentsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
setOrderBy({
|
||||
const currentOrderBy = {
|
||||
columnName: sorter.field as string,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||
order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc',
|
||||
};
|
||||
setOrderBy(currentOrderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(
|
||||
currentOrderBy,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -333,6 +364,10 @@ function K8sDeploymentsList({
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setselectedDeploymentUID(record.deploymentUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.DEPLOYMENT_UID]: record.deploymentUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@ -359,6 +394,11 @@ function K8sDeploymentsList({
|
||||
setSelectedRowData(null);
|
||||
setGroupBy([]);
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
};
|
||||
|
||||
const expandedRowRender = (): JSX.Element => (
|
||||
@ -383,7 +423,9 @@ function K8sDeploymentsList({
|
||||
}}
|
||||
showHeader={false}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => setselectedDeploymentUID(record.deploymentUID),
|
||||
onClick: (): void => {
|
||||
setselectedDeploymentUID(record.deploymentUID);
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
/>
|
||||
@ -447,6 +489,20 @@ function K8sDeploymentsList({
|
||||
|
||||
const handleCloseDeploymentDetail = (): void => {
|
||||
setselectedDeploymentUID(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(
|
||||
Array.from(searchParams.entries()).filter(
|
||||
([key]) =>
|
||||
![
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.DEPLOYMENT_UID,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS,
|
||||
].includes(key),
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@ -468,6 +524,10 @@ function K8sDeploymentsList({
|
||||
// Reset pagination on switching to groupBy
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
@ -476,7 +536,7 @@ function K8sDeploymentsList({
|
||||
category: InfraMonitoringEvents.Deployment,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -3,6 +3,7 @@ import './entityEvents.styles.scss';
|
||||
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, Table, TableColumnsType } from 'antd';
|
||||
import { VIEWS } from 'components/HostMetricsDetail/constants';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
import { EventContents } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
@ -28,6 +29,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import {
|
||||
EntityDetailsEmptyContainer,
|
||||
getEntityEventsOrLogsQueryPayload,
|
||||
QUERY_KEYS,
|
||||
} from '../utils';
|
||||
|
||||
interface EventDataType {
|
||||
@ -55,7 +57,10 @@ interface IEntityEventsProps {
|
||||
startTime: number;
|
||||
endTime: number;
|
||||
};
|
||||
handleChangeEventFilters: (filters: IBuilderQuery['filters']) => void;
|
||||
handleChangeEventFilters: (
|
||||
filters: IBuilderQuery['filters'],
|
||||
view: VIEWS,
|
||||
) => void;
|
||||
filters: IBuilderQuery['filters'];
|
||||
isModalTimeSelection: boolean;
|
||||
handleTimeChange: (
|
||||
@ -103,14 +108,18 @@ export default function Events({
|
||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||
},
|
||||
filters: {
|
||||
items: [],
|
||||
items: filters.items.filter(
|
||||
(item) =>
|
||||
item.key?.key !== QUERY_KEYS.K8S_OBJECT_KIND &&
|
||||
item.key?.key !== QUERY_KEYS.K8S_OBJECT_NAME,
|
||||
),
|
||||
op: 'AND',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[currentQuery],
|
||||
[currentQuery, filters],
|
||||
);
|
||||
|
||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||
@ -243,7 +252,7 @@ export default function Events({
|
||||
{query && (
|
||||
<QueryBuilderSearch
|
||||
query={query}
|
||||
onChange={handleChangeEventFilters}
|
||||
onChange={(value): void => handleChangeEventFilters(value, VIEWS.EVENTS)}
|
||||
disableNavigationShortcuts
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -65,7 +65,6 @@ function EntityLogs({
|
||||
const getItemContent = useCallback(
|
||||
(_: number, logToRender: ILog): JSX.Element => (
|
||||
<RawLogView
|
||||
isReadOnly
|
||||
isTextOverflowEllipsisDisabled
|
||||
key={logToRender.id}
|
||||
data={logToRender}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import './entityLogs.styles.scss';
|
||||
|
||||
import { VIEWS } from 'components/HostMetricsDetail/constants';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
@ -25,7 +26,7 @@ interface Props {
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
handleChangeLogFilters: (value: IBuilderQuery['filters']) => void;
|
||||
handleChangeLogFilters: (value: IBuilderQuery['filters'], view: VIEWS) => void;
|
||||
logFilters: IBuilderQuery['filters'];
|
||||
selectedInterval: Time;
|
||||
queryKey: string;
|
||||
@ -78,7 +79,7 @@ function EntityLogsDetailedView({
|
||||
{query && (
|
||||
<QueryBuilderSearch
|
||||
query={query}
|
||||
onChange={handleChangeLogFilters}
|
||||
onChange={(value): void => handleChangeLogFilters(value, VIEWS.LOGS)}
|
||||
disableNavigationShortcuts
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -110,6 +110,7 @@ function EntityMetrics<T>({
|
||||
softMin: null,
|
||||
minTimeScale: timeRange.startTime,
|
||||
maxTimeScale: timeRange.endTime,
|
||||
enableZoom: true,
|
||||
});
|
||||
}),
|
||||
[
|
||||
@ -162,7 +163,7 @@ function EntityMetrics<T>({
|
||||
<div className="metrics-header">
|
||||
<div className="metrics-datetime-section">
|
||||
<DateTimeSelectionV2
|
||||
showAutoRefresh={false}
|
||||
showAutoRefresh
|
||||
showRefreshText={false}
|
||||
hideShareModal
|
||||
onTimeChange={handleTimeChange}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import './entityTraces.styles.scss';
|
||||
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { VIEWS } from 'components/HostMetricsDetail/constants';
|
||||
import { getListColumns } from 'components/HostMetricsDetail/HostMetricTraces/utils';
|
||||
import { ResizeTable } from 'components/ResizeTable';
|
||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
||||
@ -43,7 +44,10 @@ interface Props {
|
||||
interval: Time | CustomTimeType,
|
||||
dateTimeRange?: [number, number],
|
||||
) => void;
|
||||
handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void;
|
||||
handleChangeTracesFilters: (
|
||||
value: IBuilderQuery['filters'],
|
||||
view: VIEWS,
|
||||
) => void;
|
||||
tracesFilters: IBuilderQuery['filters'];
|
||||
selectedInterval: Time;
|
||||
queryKey: string;
|
||||
@ -164,7 +168,9 @@ function EntityTraces({
|
||||
{query && (
|
||||
<QueryBuilderSearch
|
||||
query={query}
|
||||
onChange={handleChangeTracesFilters}
|
||||
onChange={(value): void =>
|
||||
handleChangeTracesFilters(value, VIEWS.TRACES)
|
||||
}
|
||||
disableNavigationShortcuts
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -23,6 +23,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
import { useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import K8sClustersList from './Clusters/K8sClustersList';
|
||||
@ -30,6 +31,7 @@ import {
|
||||
ClustersQuickFiltersConfig,
|
||||
DaemonSetsQuickFiltersConfig,
|
||||
DeploymentsQuickFiltersConfig,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
JobsQuickFiltersConfig,
|
||||
K8sCategories,
|
||||
NamespaceQuickFiltersConfig,
|
||||
@ -50,7 +52,14 @@ import K8sVolumesList from './Volumes/K8sVolumesList';
|
||||
export default function InfraMonitoringK8s(): JSX.Element {
|
||||
const [showFilters, setShowFilters] = useState(true);
|
||||
|
||||
const [selectedCategory, setSelectedCategory] = useState(K8sCategories.PODS);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedCategory, setSelectedCategory] = useState(() => {
|
||||
const category = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CATEGORY);
|
||||
if (category) {
|
||||
return category as keyof typeof K8sCategories;
|
||||
}
|
||||
return K8sCategories.PODS;
|
||||
});
|
||||
const [quickFiltersLastUpdated, setQuickFiltersLastUpdated] = useState(-1);
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
@ -70,6 +79,12 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
// in infra monitoring k8s, we are using only one query, hence updating the 0th index of queryData
|
||||
handleChangeQueryData('filters', query.builder.queryData[0].filters);
|
||||
setQuickFiltersLastUpdated(Date.now());
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS]: JSON.stringify(
|
||||
query.builder.queryData[0].filters,
|
||||
),
|
||||
});
|
||||
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@ -295,6 +310,9 @@ export default function InfraMonitoringK8s(): JSX.Element {
|
||||
const handleCategoryChange = (key: string | string[]): void => {
|
||||
if (Array.isArray(key) && key.length > 0) {
|
||||
setSelectedCategory(key[0] as string);
|
||||
setSearchParams({
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CATEGORY]: key[0] as string,
|
||||
});
|
||||
// Reset filters
|
||||
handleChangeQueryData('filters', { items: [], op: 'and' });
|
||||
}
|
||||
|
||||
@ -13,7 +13,11 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@ -31,6 +35,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@ -80,11 +85,27 @@ function JobDetails({
|
||||
selectedTime as Time,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(() => {
|
||||
const view = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
if (view) {
|
||||
return view as VIEWS;
|
||||
}
|
||||
return VIEWS.METRICS;
|
||||
});
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => ({
|
||||
const initialFilters = useMemo(() => {
|
||||
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
const queryKey =
|
||||
urlView === VIEW_TYPES.LOGS
|
||||
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -114,12 +135,18 @@ function JobDetails({
|
||||
value: job?.meta.k8s_namespace_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[job?.meta.k8s_job_name, job?.meta.k8s_namespace_name],
|
||||
);
|
||||
};
|
||||
}, [job?.meta.k8s_job_name, job?.meta.k8s_namespace_name, searchParams]);
|
||||
|
||||
const initialEventsFilters = useMemo(
|
||||
() => ({
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -149,9 +176,8 @@ function JobDetails({
|
||||
value: job?.meta.k8s_job_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[job?.meta.k8s_job_name],
|
||||
);
|
||||
};
|
||||
}, [job?.meta.k8s_job_name, searchParams]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@ -192,6 +218,13 @@ function JobDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(null),
|
||||
});
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@ -231,7 +264,7 @@ function JobDetails({
|
||||
);
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_JOB_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
@ -253,7 +286,7 @@ function JobDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
@ -261,6 +294,16 @@ function JobDetails({
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -268,7 +311,7 @@ function JobDetails({
|
||||
);
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_JOB_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
@ -285,7 +328,7 @@ function JobDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
@ -294,6 +337,16 @@ function JobDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -301,7 +354,7 @@ function JobDetails({
|
||||
);
|
||||
|
||||
const handleChangeEventsFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setEventsFilters((prevFilters) => {
|
||||
const jobKindFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
@ -319,7 +372,7 @@ function JobDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
jobKindFilter,
|
||||
@ -331,6 +384,16 @@ function JobDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -24,11 +24,14 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
@ -61,17 +64,32 @@ function K8sJobsList({
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(null);
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
|
||||
const [selectedJobUID, setselectedJobUID] = useState<string | null>(null);
|
||||
const [selectedJobUID, setselectedJobUID] = useState<string | null>(() => {
|
||||
const jobUID = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.JOB_UID);
|
||||
if (jobUID) {
|
||||
return jobUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.JOBS);
|
||||
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||
if (groupBy) {
|
||||
const decoded = decodeURIComponent(groupBy);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['groupBy'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [selectedRowData, setSelectedRowData] = useState<K8sJobsRowData | null>(
|
||||
null,
|
||||
@ -251,15 +269,26 @@ function K8sJobsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
setOrderBy({
|
||||
const currentOrderBy = {
|
||||
columnName: sorter.field as string,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||
order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc',
|
||||
};
|
||||
setOrderBy(currentOrderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(
|
||||
currentOrderBy,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -306,6 +335,10 @@ function K8sJobsList({
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setselectedJobUID(record.jobUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.JOB_UID]: record.jobUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@ -332,6 +365,11 @@ function K8sJobsList({
|
||||
setSelectedRowData(null);
|
||||
setGroupBy([]);
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
};
|
||||
|
||||
const expandedRowRender = (): JSX.Element => (
|
||||
@ -356,7 +394,9 @@ function K8sJobsList({
|
||||
}}
|
||||
showHeader={false}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => setselectedJobUID(record.jobUID),
|
||||
onClick: (): void => {
|
||||
setselectedJobUID(record.jobUID);
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
/>
|
||||
@ -420,6 +460,20 @@ function K8sJobsList({
|
||||
|
||||
const handleCloseJobDetail = (): void => {
|
||||
setselectedJobUID(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(
|
||||
Array.from(searchParams.entries()).filter(
|
||||
([key]) =>
|
||||
![
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.JOB_UID,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS,
|
||||
].includes(key),
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@ -441,6 +495,10 @@ function K8sJobsList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@ -448,7 +506,7 @@ function K8sJobsList({
|
||||
category: InfraMonitoringEvents.Job,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -7,11 +7,12 @@ import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearc
|
||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
||||
import { Filter, SlidersHorizontal } from 'lucide-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { K8sCategory } from './constants';
|
||||
import { INFRA_MONITORING_K8S_PARAMS_KEYS, K8sCategory } from './constants';
|
||||
import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel';
|
||||
import { IEntityColumn } from './utils';
|
||||
|
||||
@ -47,11 +48,19 @@ function K8sHeader({
|
||||
entity,
|
||||
}: K8sHeaderProps): JSX.Element {
|
||||
const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const currentQuery = initialQueriesMap[DataSource.METRICS];
|
||||
|
||||
const updatedCurrentQuery = useMemo(
|
||||
() => ({
|
||||
const updatedCurrentQuery = useMemo(() => {
|
||||
const urlFilters = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS);
|
||||
let { filters } = currentQuery.builder.queryData[0];
|
||||
if (urlFilters) {
|
||||
const decoded = decodeURIComponent(urlFilters);
|
||||
const parsed = JSON.parse(decoded);
|
||||
filters = parsed;
|
||||
}
|
||||
return {
|
||||
...currentQuery,
|
||||
builder: {
|
||||
...currentQuery.builder,
|
||||
@ -62,20 +71,24 @@ function K8sHeader({
|
||||
aggregateAttribute: {
|
||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
||||
},
|
||||
filters,
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
[currentQuery],
|
||||
);
|
||||
};
|
||||
}, [currentQuery, searchParams]);
|
||||
|
||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
||||
|
||||
const handleChangeTagFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
handleFiltersChange(value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS]: JSON.stringify(value),
|
||||
});
|
||||
},
|
||||
[handleFiltersChange],
|
||||
[handleFiltersChange, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@ -23,11 +23,14 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
@ -60,19 +63,36 @@ function K8sNamespacesList({
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(null);
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
|
||||
const [selectedNamespaceUID, setselectedNamespaceUID] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
>(() => {
|
||||
const namespaceUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.NAMESPACE_UID,
|
||||
);
|
||||
if (namespaceUID) {
|
||||
return namespaceUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.NAMESPACES);
|
||||
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||
if (groupBy) {
|
||||
const decoded = decodeURIComponent(groupBy);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['groupBy'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@ -261,15 +281,26 @@ function K8sNamespacesList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
setOrderBy({
|
||||
const currentOrderBy = {
|
||||
columnName: sorter.field as string,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||
order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc',
|
||||
};
|
||||
setOrderBy(currentOrderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(
|
||||
currentOrderBy,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -330,6 +361,10 @@ function K8sNamespacesList({
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setselectedNamespaceUID(record.namespaceUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.NAMESPACE_UID]: record.namespaceUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@ -356,6 +391,11 @@ function K8sNamespacesList({
|
||||
setSelectedRowData(null);
|
||||
setGroupBy([]);
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
};
|
||||
|
||||
const expandedRowRender = (): JSX.Element => (
|
||||
@ -380,7 +420,9 @@ function K8sNamespacesList({
|
||||
}}
|
||||
showHeader={false}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => setselectedNamespaceUID(record.namespaceUID),
|
||||
onClick: (): void => {
|
||||
setselectedNamespaceUID(record.namespaceUID);
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
/>
|
||||
@ -444,6 +486,20 @@ function K8sNamespacesList({
|
||||
|
||||
const handleCloseNamespaceDetail = (): void => {
|
||||
setselectedNamespaceUID(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(
|
||||
Array.from(searchParams.entries()).filter(
|
||||
([key]) =>
|
||||
![
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.NAMESPACE_UID,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS,
|
||||
].includes(key),
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@ -466,6 +522,10 @@ function K8sNamespacesList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@ -473,7 +533,7 @@ function K8sNamespacesList({
|
||||
category: InfraMonitoringEvents.Namespace,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -14,7 +14,11 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||
import {
|
||||
CustomTimeType,
|
||||
@ -33,6 +37,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@ -84,11 +89,27 @@ function NamespaceDetails({
|
||||
selectedTime as Time,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(() => {
|
||||
const view = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
if (view) {
|
||||
return view as VIEWS;
|
||||
}
|
||||
return VIEWS.METRICS;
|
||||
});
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => ({
|
||||
const initialFilters = useMemo(() => {
|
||||
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
const queryKey =
|
||||
urlView === VIEW_TYPES.LOGS
|
||||
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -105,12 +126,18 @@ function NamespaceDetails({
|
||||
value: namespace?.namespaceName || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[namespace?.namespaceName],
|
||||
);
|
||||
};
|
||||
}, [namespace?.namespaceName, searchParams]);
|
||||
|
||||
const initialEventsFilters = useMemo(
|
||||
() => ({
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -140,9 +167,8 @@ function NamespaceDetails({
|
||||
value: namespace?.namespaceName || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[namespace?.namespaceName],
|
||||
);
|
||||
};
|
||||
}, [namespace?.namespaceName, searchParams]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@ -183,6 +209,13 @@ function NamespaceDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(null),
|
||||
});
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@ -222,7 +255,7 @@ function NamespaceDetails({
|
||||
);
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||
@ -244,7 +277,7 @@ function NamespaceDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
@ -252,6 +285,17 @@ function NamespaceDetails({
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -259,7 +303,7 @@ function NamespaceDetails({
|
||||
);
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_NAMESPACE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||
@ -276,7 +320,7 @@ function NamespaceDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
@ -285,6 +329,16 @@ function NamespaceDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -292,7 +346,7 @@ function NamespaceDetails({
|
||||
);
|
||||
|
||||
const handleChangeEventsFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setEventsFilters((prevFilters) => {
|
||||
const namespaceKindFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
@ -310,7 +364,7 @@ function NamespaceDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
namespaceKindFilter,
|
||||
@ -322,6 +376,16 @@ function NamespaceDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -23,11 +23,14 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
@ -60,17 +63,32 @@ function K8sNodesList({
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>({ columnName: 'cpu', order: 'desc' });
|
||||
} | null>(() => getOrderByFromParams(searchParams, false));
|
||||
|
||||
const [selectedNodeUID, setselectedNodeUID] = useState<string | null>(null);
|
||||
const [selectedNodeUID, setSelectedNodeUID] = useState<string | null>(() => {
|
||||
const nodeUID = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.NODE_UID);
|
||||
if (nodeUID) {
|
||||
return nodeUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.NODES);
|
||||
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||
if (groupBy) {
|
||||
const decoded = decodeURIComponent(groupBy);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['groupBy'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [selectedRowData, setSelectedRowData] = useState<K8sNodesRowData | null>(
|
||||
null,
|
||||
@ -250,15 +268,26 @@ function K8sNodesList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
setOrderBy({
|
||||
const currentOrderBy = {
|
||||
columnName: sorter.field as string,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||
order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc',
|
||||
};
|
||||
setOrderBy(currentOrderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(
|
||||
currentOrderBy,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -307,7 +336,11 @@ function K8sNodesList({
|
||||
const handleRowClick = (record: K8sNodesRowData): void => {
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setselectedNodeUID(record.nodeUID);
|
||||
setSelectedNodeUID(record.nodeUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.NODE_UID]: record.nodeUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@ -334,6 +367,11 @@ function K8sNodesList({
|
||||
setSelectedRowData(null);
|
||||
setGroupBy([]);
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
};
|
||||
|
||||
const expandedRowRender = (): JSX.Element => (
|
||||
@ -359,7 +397,9 @@ function K8sNodesList({
|
||||
}}
|
||||
showHeader={false}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => setselectedNodeUID(record.nodeUID),
|
||||
onClick: (): void => {
|
||||
setSelectedNodeUID(record.nodeUID);
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
/>
|
||||
@ -422,7 +462,21 @@ function K8sNodesList({
|
||||
};
|
||||
|
||||
const handleCloseNodeDetail = (): void => {
|
||||
setselectedNodeUID(null);
|
||||
setSelectedNodeUID(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(
|
||||
Array.from(searchParams.entries()).filter(
|
||||
([key]) =>
|
||||
![
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.NODE_UID,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS,
|
||||
].includes(key),
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@ -444,6 +498,10 @@ function K8sNodesList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@ -451,7 +509,7 @@ function K8sNodesList({
|
||||
category: InfraMonitoringEvents.Node,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -14,8 +14,14 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import {
|
||||
filterDuplicateFilters,
|
||||
getFiltersFromParams,
|
||||
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import NodeEvents from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents';
|
||||
import {
|
||||
CustomTimeType,
|
||||
@ -34,6 +40,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@ -82,11 +89,27 @@ function NodeDetails({
|
||||
selectedTime as Time,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(() => {
|
||||
const view = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
if (view) {
|
||||
return view as VIEWS;
|
||||
}
|
||||
return VIEWS.METRICS;
|
||||
});
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => ({
|
||||
const initialFilters = useMemo(() => {
|
||||
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
const queryKey =
|
||||
urlView === VIEW_TYPES.LOGS
|
||||
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -103,12 +126,18 @@ function NodeDetails({
|
||||
value: node?.meta.k8s_node_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[node?.meta.k8s_node_name],
|
||||
);
|
||||
};
|
||||
}, [node?.meta.k8s_node_name, searchParams]);
|
||||
|
||||
const initialEventsFilters = useMemo(
|
||||
() => ({
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -138,9 +167,8 @@ function NodeDetails({
|
||||
value: node?.meta.k8s_node_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[node?.meta.k8s_node_name],
|
||||
);
|
||||
};
|
||||
}, [node?.meta.k8s_node_name, searchParams]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@ -181,6 +209,13 @@ function NodeDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(null),
|
||||
});
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@ -220,7 +255,7 @@ function NodeDetails({
|
||||
);
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||
@ -242,7 +277,7 @@ function NodeDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -252,6 +287,16 @@ function NodeDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -259,7 +304,7 @@ function NodeDetails({
|
||||
);
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_NODE_NAME, QUERY_KEYS.K8S_CLUSTER_NAME].includes(
|
||||
@ -276,7 +321,7 @@ function NodeDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -287,6 +332,16 @@ function NodeDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -294,7 +349,7 @@ function NodeDetails({
|
||||
);
|
||||
|
||||
const handleChangeEventsFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setEventsFilters((prevFilters) => {
|
||||
const nodeKindFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
@ -312,7 +367,7 @@ function NodeDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
nodeKindFilter,
|
||||
@ -324,6 +379,16 @@ function NodeDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -24,11 +24,14 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { ChevronDown, ChevronRight, CornerDownRight } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
@ -59,6 +62,7 @@ function K8sPodsList({
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
@ -68,7 +72,15 @@ function K8sPodsList({
|
||||
defaultAvailableColumns,
|
||||
);
|
||||
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||
if (groupBy) {
|
||||
const decoded = decodeURIComponent(groupBy);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['groupBy'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [selectedRowData, setSelectedRowData] = useState<K8sPodsRowData | null>(
|
||||
null,
|
||||
@ -134,9 +146,15 @@ function K8sPodsList({
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>({ columnName: 'cpu', order: 'desc' });
|
||||
} | null>(() => getOrderByFromParams(searchParams, false));
|
||||
|
||||
const [selectedPodUID, setSelectedPodUID] = useState<string | null>(null);
|
||||
const [selectedPodUID, setSelectedPodUID] = useState<string | null>(() => {
|
||||
const podUID = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.POD_UID);
|
||||
if (podUID) {
|
||||
return podUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.PODS);
|
||||
|
||||
@ -265,15 +283,26 @@ function K8sPodsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
setOrderBy({
|
||||
const currentOrderBy = {
|
||||
columnName: sorter.field as string,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||
order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc',
|
||||
};
|
||||
setOrderBy(currentOrderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(
|
||||
currentOrderBy,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -318,6 +347,10 @@ function K8sPodsList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@ -325,7 +358,7 @@ function K8sPodsList({
|
||||
category: InfraMonitoringEvents.Pod,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -366,6 +399,10 @@ function K8sPodsList({
|
||||
const handleRowClick = (record: K8sPodsRowData): void => {
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedPodUID(record.podUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.POD_UID]: record.podUID,
|
||||
});
|
||||
setSelectedRowData(null);
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
@ -380,6 +417,20 @@ function K8sPodsList({
|
||||
|
||||
const handleClosePodDetail = (): void => {
|
||||
setSelectedPodUID(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(
|
||||
Array.from(searchParams.entries()).filter(
|
||||
([key]) =>
|
||||
![
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.POD_UID,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS,
|
||||
].includes(key),
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddColumn = useCallback(
|
||||
@ -435,6 +486,11 @@ function K8sPodsList({
|
||||
setSelectedRowData(null);
|
||||
setGroupBy([]);
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
};
|
||||
|
||||
const expandedRowRender = (): JSX.Element => (
|
||||
@ -459,7 +515,9 @@ function K8sPodsList({
|
||||
indicator: <Spin indicator={<LoadingOutlined size={14} spin />} />,
|
||||
}}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => setSelectedPodUID(record.podUID),
|
||||
onClick: (): void => {
|
||||
setSelectedPodUID(record.podUID);
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
/>
|
||||
|
||||
@ -15,8 +15,14 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import {
|
||||
filterDuplicateFilters,
|
||||
getFiltersFromParams,
|
||||
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||
import {
|
||||
CustomTimeType,
|
||||
@ -35,6 +41,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@ -86,11 +93,27 @@ function PodDetails({
|
||||
selectedTime as Time,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(() => {
|
||||
const view = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
if (view) {
|
||||
return view as VIEWS;
|
||||
}
|
||||
return VIEWS.METRICS;
|
||||
});
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => ({
|
||||
const initialFilters = useMemo(() => {
|
||||
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
const queryKey =
|
||||
urlView === VIEW_TYPES.LOGS
|
||||
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -120,12 +143,18 @@ function PodDetails({
|
||||
value: pod?.meta.k8s_namespace_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[pod?.meta.k8s_namespace_name, pod?.meta.k8s_pod_name],
|
||||
);
|
||||
};
|
||||
}, [pod?.meta.k8s_namespace_name, pod?.meta.k8s_pod_name, searchParams]);
|
||||
|
||||
const initialEventsFilters = useMemo(
|
||||
() => ({
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -155,9 +184,8 @@ function PodDetails({
|
||||
value: pod?.meta.k8s_pod_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[pod?.meta.k8s_pod_name],
|
||||
);
|
||||
};
|
||||
}, [pod?.meta.k8s_pod_name, searchParams]);
|
||||
|
||||
const [logsAndTracesFilters, setLogsAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@ -198,6 +226,13 @@ function PodDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(null),
|
||||
});
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@ -237,7 +272,7 @@ function PodDetails({
|
||||
);
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogsAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[
|
||||
@ -261,7 +296,7 @@ function PodDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -271,6 +306,16 @@ function PodDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -278,7 +323,7 @@ function PodDetails({
|
||||
);
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogsAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[
|
||||
@ -297,7 +342,7 @@ function PodDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
@ -308,6 +353,16 @@ function PodDetails({
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -315,7 +370,7 @@ function PodDetails({
|
||||
);
|
||||
|
||||
const handleChangeEventsFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setEventsFilters((prevFilters) => {
|
||||
const podKindFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
@ -333,7 +388,7 @@ function PodDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
podKindFilter,
|
||||
@ -345,6 +400,16 @@ function PodDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -24,11 +24,14 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
@ -60,19 +63,36 @@ function K8sStatefulSetsList({
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(null);
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
|
||||
const [selectedStatefulSetUID, setselectedStatefulSetUID] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
>(() => {
|
||||
const statefulSetUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.STATEFULSET_UID,
|
||||
);
|
||||
if (statefulSetUID) {
|
||||
return statefulSetUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.STATEFULSETS);
|
||||
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||
if (groupBy) {
|
||||
const decoded = decodeURIComponent(groupBy);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['groupBy'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@ -263,15 +283,26 @@ function K8sStatefulSetsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
setOrderBy({
|
||||
const currentOrderBy = {
|
||||
columnName: sorter.field as string,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||
order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc',
|
||||
};
|
||||
setOrderBy(currentOrderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(
|
||||
currentOrderBy,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -330,6 +361,10 @@ function K8sStatefulSetsList({
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setselectedStatefulSetUID(record.statefulsetUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.STATEFULSET_UID]: record.statefulsetUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@ -356,6 +391,11 @@ function K8sStatefulSetsList({
|
||||
setSelectedRowData(null);
|
||||
setGroupBy([]);
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
};
|
||||
|
||||
const expandedRowRender = (): JSX.Element => (
|
||||
@ -380,7 +420,9 @@ function K8sStatefulSetsList({
|
||||
}}
|
||||
showHeader={false}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => setselectedStatefulSetUID(record.statefulsetUID),
|
||||
onClick: (): void => {
|
||||
setselectedStatefulSetUID(record.statefulsetUID);
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
/>
|
||||
@ -444,6 +486,20 @@ function K8sStatefulSetsList({
|
||||
|
||||
const handleCloseStatefulSetDetail = (): void => {
|
||||
setselectedStatefulSetUID(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(
|
||||
Array.from(searchParams.entries()).filter(
|
||||
([key]) =>
|
||||
![
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.STATEFULSET_UID,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS,
|
||||
].includes(key),
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@ -465,6 +521,10 @@ function K8sStatefulSetsList({
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setExpandedRowKeys([]);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@ -472,7 +532,7 @@ function K8sStatefulSetsList({
|
||||
category: InfraMonitoringEvents.StatefulSet,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -13,7 +13,11 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import EntityEvents from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents';
|
||||
import EntityLogs from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityLogs';
|
||||
import EntityMetrics from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityMetrics';
|
||||
@ -36,6 +40,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import {
|
||||
@ -83,11 +88,27 @@ function StatefulSetDetails({
|
||||
selectedTime as Time,
|
||||
);
|
||||
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [selectedView, setSelectedView] = useState<VIEWS>(() => {
|
||||
const view = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
if (view) {
|
||||
return view as VIEWS;
|
||||
}
|
||||
return VIEWS.METRICS;
|
||||
});
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
const initialFilters = useMemo(
|
||||
() => ({
|
||||
const initialFilters = useMemo(() => {
|
||||
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
|
||||
const queryKey =
|
||||
urlView === VIEW_TYPES.LOGS
|
||||
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
|
||||
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
|
||||
const filters = getFiltersFromParams(searchParams, queryKey);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -117,15 +138,22 @@ function StatefulSetDetails({
|
||||
value: statefulSet?.meta.k8s_namespace_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[
|
||||
};
|
||||
}, [
|
||||
searchParams,
|
||||
statefulSet?.meta.k8s_statefulset_name,
|
||||
statefulSet?.meta.k8s_namespace_name,
|
||||
],
|
||||
);
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(
|
||||
() => ({
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
items: [
|
||||
{
|
||||
@ -155,9 +183,8 @@ function StatefulSetDetails({
|
||||
value: statefulSet?.meta.k8s_statefulset_name || '',
|
||||
},
|
||||
],
|
||||
}),
|
||||
[statefulSet?.meta.k8s_statefulset_name],
|
||||
);
|
||||
};
|
||||
}, [searchParams, statefulSet?.meta.k8s_statefulset_name]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@ -198,6 +225,13 @@ function StatefulSetDetails({
|
||||
|
||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
||||
setSelectedView(e.target.value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(null),
|
||||
});
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@ -237,7 +271,7 @@ function StatefulSetDetails({
|
||||
);
|
||||
|
||||
const handleChangeLogFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
@ -260,7 +294,7 @@ function StatefulSetDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
@ -268,6 +302,16 @@ function StatefulSetDetails({
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -275,7 +319,7 @@ function StatefulSetDetails({
|
||||
);
|
||||
|
||||
const handleChangeTracesFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setLogAndTracesFilters((prevFilters) => {
|
||||
const primaryFilters = prevFilters.items.filter((item) =>
|
||||
[QUERY_KEYS.K8S_STATEFUL_SET_NAME, QUERY_KEYS.K8S_NAMESPACE_NAME].includes(
|
||||
@ -292,7 +336,7 @@ function StatefulSetDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...primaryFilters,
|
||||
@ -301,6 +345,16 @@ function StatefulSetDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@ -308,7 +362,7 @@ function StatefulSetDetails({
|
||||
);
|
||||
|
||||
const handleChangeEventsFilters = useCallback(
|
||||
(value: IBuilderQuery['filters']) => {
|
||||
(value: IBuilderQuery['filters'], view: VIEWS) => {
|
||||
setEventsFilters((prevFilters) => {
|
||||
const statefulSetKindFilter = prevFilters.items.find(
|
||||
(item) => item.key?.key === QUERY_KEYS.K8S_OBJECT_KIND,
|
||||
@ -326,7 +380,7 @@ function StatefulSetDetails({
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
statefulSetKindFilter,
|
||||
@ -338,6 +392,16 @@ function StatefulSetDetails({
|
||||
),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@ -24,11 +24,14 @@ import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
K8sEntityToAggregateAttributeMapping,
|
||||
} from '../constants';
|
||||
@ -60,19 +63,36 @@ function K8sVolumesList({
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(null);
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
|
||||
const [selectedVolumeUID, setselectedVolumeUID] = useState<string | null>(
|
||||
null,
|
||||
() => {
|
||||
const volumeUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VOLUME_UID,
|
||||
);
|
||||
if (volumeUID) {
|
||||
return volumeUID;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.VOLUMES);
|
||||
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>([]);
|
||||
const [groupBy, setGroupBy] = useState<IBuilderQuery['groupBy']>(() => {
|
||||
const groupBy = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY);
|
||||
if (groupBy) {
|
||||
const decoded = decodeURIComponent(groupBy);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['groupBy'];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@ -253,15 +273,26 @@ function K8sVolumesList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
setOrderBy({
|
||||
const currentOrderBy = {
|
||||
columnName: sorter.field as string,
|
||||
order: sorter.order === 'ascend' ? 'asc' : 'desc',
|
||||
order: (sorter.order === 'ascend' ? 'asc' : 'desc') as 'asc' | 'desc',
|
||||
};
|
||||
setOrderBy(currentOrderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(
|
||||
currentOrderBy,
|
||||
),
|
||||
});
|
||||
} else {
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
[searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@ -315,6 +346,10 @@ function K8sVolumesList({
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setselectedVolumeUID(record.volumeUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VOLUME_UID]: record.volumeUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@ -341,6 +376,11 @@ function K8sVolumesList({
|
||||
setSelectedRowData(null);
|
||||
setGroupBy([]);
|
||||
setOrderBy(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify([]),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(null),
|
||||
});
|
||||
};
|
||||
|
||||
const expandedRowRender = (): JSX.Element => (
|
||||
@ -365,7 +405,9 @@ function K8sVolumesList({
|
||||
}}
|
||||
showHeader={false}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => setselectedVolumeUID(record.volumeUID),
|
||||
onClick: (): void => {
|
||||
setselectedVolumeUID(record.volumeUID);
|
||||
},
|
||||
className: 'expanded-clickable-row',
|
||||
})}
|
||||
/>
|
||||
@ -429,6 +471,13 @@ function K8sVolumesList({
|
||||
|
||||
const handleCloseVolumeDetail = (): void => {
|
||||
setselectedVolumeUID(null);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(
|
||||
Array.from(searchParams.entries()).filter(
|
||||
([key]) => key !== INFRA_MONITORING_K8S_PARAMS_KEYS.VOLUME_UID,
|
||||
),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@ -449,6 +498,10 @@ function K8sVolumesList({
|
||||
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
setExpandedRowKeys([]);
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
@ -457,7 +510,7 @@ function K8sVolumesList({
|
||||
category: InfraMonitoringEvents.Volumes,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData],
|
||||
[groupByFiltersData?.payload?.attributeKeys, searchParams, setSearchParams],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -12,9 +12,16 @@ import { ResizeTable } from 'components/ResizeTable';
|
||||
import FieldRenderer from 'container/LogDetailedView/FieldRenderer';
|
||||
import { DataType } from 'container/LogDetailedView/TableView';
|
||||
import { useMemo } from 'react';
|
||||
import { TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import { getInvalidValueTooltipText, K8sCategory } from './constants';
|
||||
import {
|
||||
getInvalidValueTooltipText,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from './constants';
|
||||
|
||||
/**
|
||||
* Converts size in bytes to a human-readable string with appropriate units
|
||||
@ -250,3 +257,37 @@ export const filterDuplicateFilters = (
|
||||
|
||||
return uniqueFilters;
|
||||
};
|
||||
|
||||
export const getOrderByFromParams = (
|
||||
searchParams: URLSearchParams,
|
||||
returnNullAsDefault = false,
|
||||
): {
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null => {
|
||||
const orderByFromParams = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY,
|
||||
);
|
||||
if (orderByFromParams) {
|
||||
const decoded = decodeURIComponent(orderByFromParams);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as { columnName: string; order: 'asc' | 'desc' };
|
||||
}
|
||||
if (returnNullAsDefault) {
|
||||
return null;
|
||||
}
|
||||
return { columnName: 'cpu', order: 'desc' };
|
||||
};
|
||||
|
||||
export const getFiltersFromParams = (
|
||||
searchParams: URLSearchParams,
|
||||
queryKey: string,
|
||||
): IBuilderQuery['filters'] | null => {
|
||||
const filtersFromParams = searchParams.get(queryKey);
|
||||
if (filtersFromParams) {
|
||||
const decoded = decodeURIComponent(filtersFromParams);
|
||||
const parsed = JSON.parse(decoded);
|
||||
return parsed as IBuilderQuery['filters'];
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@ -518,3 +518,24 @@ export const getInvalidValueTooltipText = (
|
||||
entity: K8sCategory,
|
||||
attribute: string,
|
||||
): string => `Some ${entity} do not have ${attribute}s.`;
|
||||
|
||||
export const INFRA_MONITORING_K8S_PARAMS_KEYS = {
|
||||
CATEGORY: 'category',
|
||||
VIEW: 'view',
|
||||
CLUSTER_NAME: 'clusterName',
|
||||
DAEMONSET_UID: 'daemonSetUID',
|
||||
DEPLOYMENT_UID: 'deploymentUID',
|
||||
JOB_UID: 'jobUID',
|
||||
NAMESPACE_UID: 'namespaceUID',
|
||||
NODE_UID: 'nodeUID',
|
||||
POD_UID: 'podUID',
|
||||
STATEFULSET_UID: 'statefulsetUID',
|
||||
VOLUME_UID: 'volumeUID',
|
||||
FILTERS: 'filters',
|
||||
GROUP_BY: 'groupBy',
|
||||
ORDER_BY: 'orderBy',
|
||||
LOG_FILTERS: 'logFilters',
|
||||
TRACES_FILTERS: 'tracesFilters',
|
||||
EVENTS_FILTERS: 'eventsFilters',
|
||||
HOSTS_FILTERS: 'hostsFilters',
|
||||
};
|
||||
|
||||
@ -19,6 +19,7 @@ import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFall
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { generateExportToDashboardLink } from 'utils/dashboard/generateExportToDashboardLink';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
@ -26,6 +27,7 @@ import { v4 as uuid } from 'uuid';
|
||||
import QuerySection from './QuerySection';
|
||||
import TimeSeries from './TimeSeries';
|
||||
import { ExplorerTabs } from './types';
|
||||
import { splitQueryIntoOneChartPerQuery } from './utils';
|
||||
|
||||
const ONE_CHART_PER_QUERY_ENABLED_KEY = 'isOneChartPerQueryEnabled';
|
||||
|
||||
@ -75,14 +77,18 @@ function Explorer(): JSX.Element {
|
||||
useShareBuilderUrl(exportDefaultQuery);
|
||||
|
||||
const handleExport = useCallback(
|
||||
(dashboard: Dashboard | null): void => {
|
||||
(
|
||||
dashboard: Dashboard | null,
|
||||
_isNewDashboard?: boolean,
|
||||
queryToExport?: Query,
|
||||
): void => {
|
||||
if (!dashboard) return;
|
||||
|
||||
const widgetId = uuid();
|
||||
|
||||
const updatedDashboard = addEmptyWidgetInDashboardJSONWithQuery(
|
||||
dashboard,
|
||||
exportDefaultQuery,
|
||||
queryToExport || exportDefaultQuery,
|
||||
widgetId,
|
||||
PANEL_TYPES.TIME_SERIES,
|
||||
options.selectColumns,
|
||||
@ -114,7 +120,7 @@ function Explorer(): JSX.Element {
|
||||
return;
|
||||
}
|
||||
const dashboardEditView = generateExportToDashboardLink({
|
||||
query: exportDefaultQuery,
|
||||
query: queryToExport || exportDefaultQuery,
|
||||
panelType: PANEL_TYPES.TIME_SERIES,
|
||||
dashboardId: data.payload?.uuid || '',
|
||||
widgetId,
|
||||
@ -135,6 +141,14 @@ function Explorer(): JSX.Element {
|
||||
[exportDefaultQuery, notifications, updateDashboard],
|
||||
);
|
||||
|
||||
const splitedQueries = useMemo(
|
||||
() =>
|
||||
splitQueryIntoOneChartPerQuery(
|
||||
stagedQuery || initialQueriesMap[DataSource.METRICS],
|
||||
),
|
||||
[stagedQuery],
|
||||
);
|
||||
|
||||
return (
|
||||
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
|
||||
<div className="metrics-explorer-explore-container">
|
||||
@ -190,6 +204,8 @@ function Explorer(): JSX.Element {
|
||||
isLoading={isLoading}
|
||||
sourcepage={DataSource.METRICS}
|
||||
onExport={handleExport}
|
||||
isOneChartPerQuery={showOneChartPerQuery}
|
||||
splitedQueries={splitedQueries}
|
||||
/>
|
||||
</Sentry.ErrorBoundary>
|
||||
);
|
||||
|
||||
@ -75,7 +75,7 @@ function MetricDetails({
|
||||
hour."
|
||||
placement="top"
|
||||
>
|
||||
<span>{`${timeSeriesTotal} ⎯ ${timeSeriesActive} active`}</span>
|
||||
<span>{`${timeSeriesTotal} total ⎯ ${timeSeriesActive} active`}</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}, [metric]);
|
||||
|
||||
@ -154,10 +154,14 @@ function MetricsTreemap({
|
||||
<foreignObject
|
||||
width={nodeWidth}
|
||||
height={nodeHeight}
|
||||
style={getTreemapTileStyle(node.data)}
|
||||
onClick={(): void => openMetricDetails(node.data.id)}
|
||||
>
|
||||
<div style={getTreemapTileTextStyle()}>
|
||||
<div
|
||||
style={{
|
||||
...getTreemapTileStyle(node.data),
|
||||
...getTreemapTileTextStyle(),
|
||||
}}
|
||||
>
|
||||
{`${node.data.displayValue}%`}
|
||||
</div>
|
||||
</foreignObject>
|
||||
|
||||
@ -385,6 +385,7 @@ function NewWidget({ selectedGraph }: NewWidgetProps): JSX.Element {
|
||||
getGraphTypeForFormat(selectedGraph || selectedWidget.panelTypes) ===
|
||||
PANEL_TYPES.TABLE,
|
||||
variables: getDashboardVariables(selectedDashboard?.data.variables),
|
||||
originalGraphType: selectedGraph || selectedWidget?.panelTypes,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { updateStepInterval } from 'container/GridCardLayout/utils';
|
||||
import {
|
||||
GetMetricQueryRange,
|
||||
GetQueryResultsProps,
|
||||
} from 'lib/dashboard/getQueryResults';
|
||||
import getStartEndRangeTime from 'lib/getStartEndRangeTime';
|
||||
import { useMemo } from 'react';
|
||||
import { useQuery, UseQueryOptions, UseQueryResult } from 'react-query';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
@ -75,9 +77,32 @@ export const useGetQueryRange: UseGetQueryRange = (
|
||||
return [REACT_QUERY_KEY.GET_QUERY_RANGE, newRequestData];
|
||||
}, [options?.queryKey, newRequestData]);
|
||||
|
||||
const modifiedRequestData = useMemo(() => {
|
||||
const graphType = requestData.originalGraphType || requestData.graphType;
|
||||
if (graphType === PANEL_TYPES.BAR) {
|
||||
const { start, end } = getStartEndRangeTime({
|
||||
type: requestData.selectedTime,
|
||||
interval: requestData.globalSelectedInterval,
|
||||
});
|
||||
|
||||
const updatedQuery = updateStepInterval(
|
||||
requestData.query,
|
||||
requestData.start ? requestData.start * 1e3 : parseInt(start, 10) * 1e3,
|
||||
requestData.end ? requestData.end * 1e3 : parseInt(end, 10) * 1e3,
|
||||
);
|
||||
|
||||
return {
|
||||
...requestData,
|
||||
query: updatedQuery,
|
||||
};
|
||||
}
|
||||
|
||||
return requestData;
|
||||
}, [requestData]);
|
||||
|
||||
return useQuery<SuccessResponse<MetricRangePayloadProps>, Error>({
|
||||
queryFn: async ({ signal }) =>
|
||||
GetMetricQueryRange(requestData, version, signal, headers),
|
||||
GetMetricQueryRange(modifiedRequestData, version, signal, headers),
|
||||
...options,
|
||||
queryKey,
|
||||
});
|
||||
|
||||
@ -103,4 +103,5 @@ export interface GetQueryResultsProps {
|
||||
start?: number;
|
||||
end?: number;
|
||||
step?: number;
|
||||
originalGraphType?: PANEL_TYPES;
|
||||
}
|
||||
|
||||
@ -69,6 +69,7 @@ export interface GetUPlotChartOptions {
|
||||
colorMapping?: Record<string, string>;
|
||||
enhancedLegend?: boolean;
|
||||
legendPosition?: LegendPosition;
|
||||
enableZoom?: boolean;
|
||||
}
|
||||
|
||||
/** the function converts series A , series B , series C to
|
||||
@ -179,6 +180,7 @@ export const getUPlotChartOptions = ({
|
||||
colorMapping,
|
||||
enhancedLegend = true,
|
||||
legendPosition = LegendPosition.BOTTOM,
|
||||
enableZoom,
|
||||
}: GetUPlotChartOptions): uPlot.Options => {
|
||||
const timeScaleProps = getXAxisScale(minTimeScale, maxTimeScale);
|
||||
|
||||
@ -248,7 +250,25 @@ export const getUPlotChartOptions = ({
|
||||
`${u.series[seriesIdx].points.stroke(u, seriesIdx)}90`,
|
||||
fill: (): string => '#fff',
|
||||
},
|
||||
...(enableZoom
|
||||
? {
|
||||
drag: {
|
||||
x: true,
|
||||
y: true,
|
||||
},
|
||||
focus: {
|
||||
prox: 30,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
...(enableZoom
|
||||
? {
|
||||
select: {
|
||||
show: true,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
tzDate,
|
||||
padding: [16, 16, 8, 8],
|
||||
bands,
|
||||
|
||||
@ -6,7 +6,7 @@ import { useMemo } from 'react';
|
||||
import TrimmedText from '../TrimmedText/TrimmedText';
|
||||
|
||||
type KeyValueLabelProps = {
|
||||
badgeKey: string;
|
||||
badgeKey: string | React.ReactNode;
|
||||
badgeValue: string;
|
||||
maxCharacters?: number;
|
||||
};
|
||||
@ -25,7 +25,11 @@ export default function KeyValueLabel({
|
||||
return (
|
||||
<div className="key-value-label">
|
||||
<div className="key-value-label__key">
|
||||
{typeof badgeKey === 'string' ? (
|
||||
<TrimmedText text={badgeKey} maxCharacters={maxCharacters} />
|
||||
) : (
|
||||
badgeKey
|
||||
)}
|
||||
</div>
|
||||
{isUrl ? (
|
||||
<a
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import getAllOrgPreferences from 'api/preferences/getAllOrgPreferences';
|
||||
import { Logout } from 'api/utils';
|
||||
import getUserVersion from 'api/v1/version/getVersion';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import dayjs from 'dayjs';
|
||||
import useActiveLicenseV3 from 'hooks/useActiveLicenseV3/useActiveLicenseV3';
|
||||
@ -151,6 +152,12 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
enabled: !!isLoggedIn && !!user.email && user.role === USER_ROLES.ADMIN,
|
||||
});
|
||||
|
||||
const { data: versionData } = useQuery({
|
||||
queryFn: getUserVersion,
|
||||
queryKey: ['getUserVersion', user?.accessJwt],
|
||||
enabled: isLoggedIn,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!isFetchingOrgPreferences &&
|
||||
@ -246,6 +253,7 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
updateUser,
|
||||
updateOrgPreferences,
|
||||
updateOrg,
|
||||
versionData: versionData?.payload || null,
|
||||
}),
|
||||
[
|
||||
trialInfo,
|
||||
@ -265,6 +273,7 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||
updateOrg,
|
||||
user,
|
||||
userFetchError,
|
||||
versionData,
|
||||
],
|
||||
);
|
||||
return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
|
||||
|
||||
@ -3,6 +3,7 @@ import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeatures
|
||||
import { LicenseResModel, TrialInfo } from 'types/api/licensesV3/getActive';
|
||||
import { Organization } from 'types/api/user/getOrganization';
|
||||
import { UserResponse as User } from 'types/api/user/getUser';
|
||||
import { PayloadProps } from 'types/api/user/getVersion';
|
||||
import { OrgPreference } from 'types/reducer/app';
|
||||
|
||||
export interface IAppContext {
|
||||
@ -25,6 +26,7 @@ export interface IAppContext {
|
||||
updateUser: (user: IUser) => void;
|
||||
updateOrgPreferences: (orgPreferences: OrgPreference[]) => void;
|
||||
updateOrg(orgId: string, updatedOrgName: string): void;
|
||||
versionData: PayloadProps | null;
|
||||
}
|
||||
|
||||
// User
|
||||
|
||||
60
frontend/src/providers/ErrorModalProvider.tsx
Normal file
60
frontend/src/providers/ErrorModalProvider.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import ErrorModal from 'components/ErrorModal/ErrorModal';
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
interface ErrorModalContextType {
|
||||
showErrorModal: (error: APIError) => void;
|
||||
hideErrorModal: () => void;
|
||||
}
|
||||
|
||||
const ErrorModalContext = createContext<ErrorModalContextType | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
export function ErrorModalProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): JSX.Element {
|
||||
const [error, setError] = useState<APIError | null>(null);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
const showErrorModal = useCallback((error: APIError): void => {
|
||||
setError(error);
|
||||
setIsVisible(true);
|
||||
}, []);
|
||||
|
||||
const hideErrorModal = useCallback((): void => {
|
||||
setError(null);
|
||||
setIsVisible(false);
|
||||
}, []);
|
||||
|
||||
const value = useMemo(() => ({ showErrorModal, hideErrorModal }), [
|
||||
showErrorModal,
|
||||
hideErrorModal,
|
||||
]);
|
||||
|
||||
return (
|
||||
<ErrorModalContext.Provider value={value}>
|
||||
{children}
|
||||
{isVisible && error && (
|
||||
<ErrorModal error={error} onClose={hideErrorModal} open={isVisible} />
|
||||
)}
|
||||
</ErrorModalContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useErrorModal = (): ErrorModalContextType => {
|
||||
const context = useContext(ErrorModalContext);
|
||||
if (!context) {
|
||||
throw new Error('useErrorModal must be used within an ErrorModalProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@ -5,6 +5,7 @@ import ROUTES from 'constants/routes';
|
||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||
import { AppContext } from 'providers/App/App';
|
||||
import { IAppContext } from 'providers/App/types';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import TimezoneProvider from 'providers/Timezone';
|
||||
import React, { ReactElement } from 'react';
|
||||
@ -234,6 +235,11 @@ export function getAppContextMock(
|
||||
updateOrg: jest.fn(),
|
||||
updateOrgPreferences: jest.fn(),
|
||||
activeLicenseRefetch: jest.fn(),
|
||||
versionData: {
|
||||
version: '1.0.0',
|
||||
ee: 'Y',
|
||||
setupCompleted: true,
|
||||
},
|
||||
...appContextOverrides,
|
||||
};
|
||||
}
|
||||
@ -249,6 +255,7 @@ function AllTheProviders({
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ResourceProvider>
|
||||
<ErrorModalProvider>
|
||||
<Provider store={mockStored(role)}>
|
||||
<AppContext.Provider value={getAppContextMock(role, appContextOverrides)}>
|
||||
<BrowserRouter>
|
||||
@ -259,6 +266,7 @@ function AllTheProviders({
|
||||
</BrowserRouter>
|
||||
</AppContext.Provider>
|
||||
</Provider>
|
||||
</ErrorModalProvider>
|
||||
</ResourceProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user