mirror of
https://github.com/SigNoz/signoz.git
synced 2026-03-20 03:30:26 +00:00
Compare commits
8 Commits
refactor/c
...
fix/infra-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d42a30761 | ||
|
|
5b814eb6a5 | ||
|
|
78246fdc6a | ||
|
|
4849c8ef4b | ||
|
|
c8fcc48022 | ||
|
|
44b6885639 | ||
|
|
0e5a128325 | ||
|
|
fd19ff8e5e |
@@ -162,6 +162,7 @@
|
||||
"vite-plugin-html": "3.2.2",
|
||||
"web-vitals": "^0.2.4",
|
||||
"xstate": "^4.31.0",
|
||||
"zod": "4.3.6",
|
||||
"zustand": "5.0.11"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -284,4 +285,4 @@
|
||||
"tmp": "0.2.4",
|
||||
"vite": "npm:rolldown-vite@7.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { VerticalAlignTopOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
@@ -11,15 +10,16 @@ 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';
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringFiltersHosts,
|
||||
useInfraMonitoringOrderByHosts,
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import { usePageSize } from 'container/InfraMonitoringK8s/utils';
|
||||
import { useGetHostList } from 'hooks/infraMonitoring/useGetHostList';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
|
||||
import { Filter } from 'lucide-react';
|
||||
import { parseAsString, useQueryState } from 'nuqs';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { IBuilderQuery, Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
@@ -35,50 +35,29 @@ 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 = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS,
|
||||
);
|
||||
if (!filters) {
|
||||
return {
|
||||
items: [],
|
||||
op: 'and',
|
||||
};
|
||||
}
|
||||
return filters;
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [filters, setFilters] = useInfraMonitoringFiltersHosts();
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderByHosts();
|
||||
|
||||
const [showFilters, setShowFilters] = useState<boolean>(true);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams));
|
||||
const [selectedHostName, setSelectedHostName] = useQueryState(
|
||||
'hostName',
|
||||
parseAsString.withDefault(''),
|
||||
);
|
||||
|
||||
const handleOrderByChange = (
|
||||
orderBy: {
|
||||
orderByValue: {
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null,
|
||||
): void => {
|
||||
setOrderBy(orderBy);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY]: JSON.stringify(orderBy),
|
||||
});
|
||||
setOrderBy(orderByValue);
|
||||
};
|
||||
|
||||
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');
|
||||
@@ -154,12 +133,8 @@ function HostsList(): JSX.Element {
|
||||
const handleFiltersChange = useCallback(
|
||||
(value: IBuilderQuery['filters']): void => {
|
||||
const isNewFilterAdded = value?.items?.length !== filters?.items?.length;
|
||||
setFilters(value);
|
||||
setFilters(value ?? null);
|
||||
handleChangeQueryData('filters', value);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS]: JSON.stringify(value),
|
||||
});
|
||||
if (isNewFilterAdded) {
|
||||
setCurrentPage(1);
|
||||
|
||||
@@ -171,8 +146,7 @@ function HostsList(): JSX.Element {
|
||||
}
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[filters],
|
||||
[filters, setFilters, setCurrentPage, handleChangeQueryData],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -184,7 +158,7 @@ function HostsList(): JSX.Element {
|
||||
}, [data?.payload?.data?.total]);
|
||||
|
||||
const selectedHostData = useMemo(() => {
|
||||
if (!selectedHostName) {
|
||||
if (!selectedHostName?.trim()) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
@@ -242,7 +216,7 @@ function HostsList(): JSX.Element {
|
||||
</div>
|
||||
)}
|
||||
<HostsListControls
|
||||
filters={filters}
|
||||
filters={filters ?? undefined}
|
||||
handleFiltersChange={handleFiltersChange}
|
||||
showAutoRefresh={!selectedHostData}
|
||||
/>
|
||||
@@ -260,6 +234,7 @@ function HostsList(): JSX.Element {
|
||||
pageSize={pageSize}
|
||||
setPageSize={setPageSize}
|
||||
setOrderBy={handleOrderByChange}
|
||||
orderBy={orderBy}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Skeleton,
|
||||
@@ -114,8 +114,12 @@ export default function HostsListTable({
|
||||
pageSize,
|
||||
setOrderBy,
|
||||
setPageSize,
|
||||
orderBy,
|
||||
}: HostsListTableProps): JSX.Element {
|
||||
const columns = useMemo(() => getHostsListColumns(), []);
|
||||
const [defaultOrderBy] = useState(orderBy);
|
||||
const columns = useMemo(() => getHostsListColumns(defaultOrderBy), [
|
||||
defaultOrderBy,
|
||||
]);
|
||||
|
||||
const sentAnyHostMetricsData = useMemo(
|
||||
() => data?.payload?.data?.sentAnyHostMetricsData || false,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { render } from '@testing-library/react';
|
||||
import * as useGetHostListHooks from 'hooks/infraMonitoring/useGetHostList';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import * as appContextHooks from 'providers/App/App';
|
||||
import * as timezoneHooks from 'providers/Timezone';
|
||||
import store from 'store';
|
||||
@@ -19,6 +20,16 @@ jest.mock('lib/getMinMax', () => ({
|
||||
isValidShortHandDateTimeFormat: jest.fn().mockReturnValue(true),
|
||||
})),
|
||||
}));
|
||||
jest.mock('container/TopNav/DateTimeSelectionV2', () => ({
|
||||
__esModule: true,
|
||||
default: (): JSX.Element => (
|
||||
<div data-testid="date-time-selection">Date Time</div>
|
||||
),
|
||||
}));
|
||||
jest.mock('components/HostMetricsDetail', () => ({
|
||||
__esModule: true,
|
||||
default: (): null => null,
|
||||
}));
|
||||
jest.mock('components/CustomTimePicker/CustomTimePicker', () => ({
|
||||
__esModule: true,
|
||||
default: ({ onSelect, selectedTime, selectedValue }: any): JSX.Element => (
|
||||
@@ -55,19 +66,6 @@ jest.mock('react-router-dom', () => {
|
||||
}),
|
||||
};
|
||||
});
|
||||
jest.mock('react-router-dom-v5-compat', () => {
|
||||
const actual = jest.requireActual('react-router-dom-v5-compat');
|
||||
return {
|
||||
...actual,
|
||||
useSearchParams: jest
|
||||
.fn()
|
||||
.mockReturnValue([
|
||||
{ get: jest.fn(), entries: jest.fn().mockReturnValue([]) },
|
||||
jest.fn(),
|
||||
]),
|
||||
useNavigationType: (): any => 'PUSH',
|
||||
};
|
||||
});
|
||||
jest.mock('hooks/useSafeNavigate', () => ({
|
||||
useSafeNavigate: (): any => ({
|
||||
safeNavigate: jest.fn(),
|
||||
@@ -127,29 +125,35 @@ jest.spyOn(appContextHooks, 'useAppContext').mockReturnValue({
|
||||
},
|
||||
} as any);
|
||||
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: {} });
|
||||
|
||||
describe('HostsList', () => {
|
||||
it('renders hosts list table', () => {
|
||||
const { container } = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<HostsList />
|
||||
</Provider>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<HostsList />
|
||||
</Provider>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
expect(container.querySelector('.hosts-list-table')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders filters', () => {
|
||||
const { container } = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<HostsList />
|
||||
</Provider>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>
|
||||
<Provider store={store}>
|
||||
<HostsList />
|
||||
</Provider>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
expect(container.querySelector('.filters')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -72,6 +72,7 @@ describe('HostsListTable', () => {
|
||||
pageSize: 10,
|
||||
setOrderBy: mockSetOrderBy,
|
||||
setPageSize: mockSetPageSize,
|
||||
orderBy: null,
|
||||
};
|
||||
|
||||
it('renders loading state if isLoading is true and tableData is empty', () => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { InfoCircleOutlined } from '@ant-design/icons';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Progress, TabsProps, Tag, Tooltip, Typography } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { SortOrder } from 'antd/lib/table/interface';
|
||||
import {
|
||||
HostData,
|
||||
HostListPayload,
|
||||
@@ -20,6 +21,7 @@ import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { OrderBySchemaType } from '../InfraMonitoringK8s/schemas';
|
||||
import HostsList from './HostsList';
|
||||
|
||||
import './InfraMonitoring.styles.scss';
|
||||
@@ -105,6 +107,7 @@ export interface HostsListTableProps {
|
||||
orderBy: { columnName: string; order: 'asc' | 'desc' } | null,
|
||||
) => void;
|
||||
setPageSize: (pageSize: number) => void;
|
||||
orderBy: OrderBySchemaType;
|
||||
}
|
||||
|
||||
export interface EmptyOrLoadingViewProps {
|
||||
@@ -127,6 +130,17 @@ export const getHostListsQuery = (): HostListPayload => ({
|
||||
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||
});
|
||||
|
||||
function mapOrderByToSortOrder(
|
||||
column: string,
|
||||
orderBy: OrderBySchemaType,
|
||||
): SortOrder | undefined {
|
||||
return orderBy?.columnName === column
|
||||
? orderBy?.order === 'asc'
|
||||
? 'ascend'
|
||||
: 'descend'
|
||||
: undefined;
|
||||
}
|
||||
|
||||
export const getTabsItems = (): TabsProps['items'] => [
|
||||
{
|
||||
label: <TabLabel label="List View" isDisabled={false} tooltipText="" />,
|
||||
@@ -135,7 +149,9 @@ export const getTabsItems = (): TabsProps['items'] => [
|
||||
},
|
||||
];
|
||||
|
||||
export const getHostsListColumns = (): ColumnType<HostRowData>[] => [
|
||||
export const getHostsListColumns = (
|
||||
orderBy: OrderBySchemaType,
|
||||
): ColumnType<HostRowData>[] => [
|
||||
{
|
||||
title: <div className="hostname-column-header">Hostname</div>,
|
||||
dataIndex: 'hostName',
|
||||
@@ -164,6 +180,7 @@ export const getHostsListColumns = (): ColumnType<HostRowData>[] => [
|
||||
key: 'cpu',
|
||||
width: 100,
|
||||
sorter: true,
|
||||
defaultSortOrder: mapOrderByToSortOrder('cpu', orderBy),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
@@ -179,6 +196,7 @@ export const getHostsListColumns = (): ColumnType<HostRowData>[] => [
|
||||
key: 'memory',
|
||||
width: 100,
|
||||
sorter: true,
|
||||
defaultSortOrder: mapOrderByToSortOrder('memory', orderBy),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
@@ -187,6 +205,7 @@ export const getHostsListColumns = (): ColumnType<HostRowData>[] => [
|
||||
key: 'wait',
|
||||
width: 100,
|
||||
sorter: true,
|
||||
defaultSortOrder: mapOrderByToSortOrder('wait', orderBy),
|
||||
align: 'right',
|
||||
},
|
||||
{
|
||||
@@ -195,6 +214,7 @@ export const getHostsListColumns = (): ColumnType<HostRowData>[] => [
|
||||
key: 'load15',
|
||||
width: 100,
|
||||
sorter: true,
|
||||
defaultSortOrder: mapOrderByToSortOrder('load15', orderBy),
|
||||
align: 'right',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
@@ -15,15 +14,15 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
filterDuplicateFilters,
|
||||
getFiltersFromParams,
|
||||
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||
import {
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -93,23 +92,21 @@ function ClusterDetails({
|
||||
: (selectedTime as Time),
|
||||
);
|
||||
|
||||
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 [selectedView, setSelectedView] = useInfraMonitoringView();
|
||||
const [logFiltersParam, setLogFiltersParam] = useInfraMonitoringLogFilters();
|
||||
const [
|
||||
tracesFiltersParam,
|
||||
setTracesFiltersParam,
|
||||
] = useInfraMonitoringTracesFilters();
|
||||
const [
|
||||
eventsFiltersParam,
|
||||
setEventsFiltersParam,
|
||||
] = useInfraMonitoringEventsFilters();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
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);
|
||||
const filters =
|
||||
selectedView === VIEW_TYPES.LOGS ? logFiltersParam : tracesFiltersParam;
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
@@ -129,15 +126,16 @@ function ClusterDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [cluster?.meta.k8s_cluster_name, searchParams]);
|
||||
}, [
|
||||
cluster?.meta.k8s_cluster_name,
|
||||
selectedView,
|
||||
logFiltersParam,
|
||||
tracesFiltersParam,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
if (eventsFiltersParam) {
|
||||
return eventsFiltersParam;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
@@ -166,7 +164,7 @@ function ClusterDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [cluster?.meta.k8s_cluster_name, searchParams]);
|
||||
}, [cluster?.meta.k8s_cluster_name, eventsFiltersParam]);
|
||||
|
||||
const [logsAndTracesFilters, setLogsAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@@ -207,13 +205,9 @@ 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),
|
||||
});
|
||||
setLogFiltersParam(null);
|
||||
setTracesFiltersParam(null);
|
||||
setEventsFiltersParam(null);
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@@ -287,13 +281,8 @@ function ClusterDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setLogFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -330,13 +319,8 @@ function ClusterDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setTracesFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -379,13 +363,8 @@ function ClusterDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setEventsFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -27,12 +26,13 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import { GetK8sEntityToAggregateAttribute, K8sCategory } from '../constants';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from '../constants';
|
||||
useInfraMonitoringClusterName,
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringOrderBy,
|
||||
} from '../hooks';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
|
||||
import '../InfraMonitoringK8s.styles.scss';
|
||||
import './K8sClustersList.styles.scss';
|
||||
|
||||
function K8sClustersList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
@@ -60,55 +61,19 @@ function K8sClustersList({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
const [
|
||||
selectedClusterName,
|
||||
setselectedClusterName,
|
||||
] = useInfraMonitoringClusterName();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams, false));
|
||||
|
||||
const [selectedClusterName, setselectedClusterName] = useState<string | 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 = 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,
|
||||
@@ -134,7 +99,7 @@ function K8sClustersList({
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
}, [quickFiltersLastUpdated, setCurrentPage]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
@@ -187,25 +152,28 @@ function K8sClustersList({
|
||||
filters,
|
||||
start: Math.floor(minTime / 1000000),
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
orderBy: orderBy || baseQuery.orderBy,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||
|
||||
const groupedByRowDataQueryKey = useMemo(() => {
|
||||
// be careful with what you serialize from selectedRowData
|
||||
// since it's react node, it could contain circular references
|
||||
const selectedRowDataKey = JSON.stringify(selectedRowData?.groupedByMeta);
|
||||
if (selectedClusterName) {
|
||||
return [
|
||||
'clusterList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'clusterList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
String(minTime),
|
||||
String(maxTime),
|
||||
];
|
||||
@@ -372,26 +340,15 @@ function K8sClustersList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
const currentOrderBy = {
|
||||
setOrderBy({
|
||||
columnName: sorter.field as string,
|
||||
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],
|
||||
[setCurrentPage, setOrderBy],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -454,10 +411,6 @@ 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);
|
||||
}
|
||||
@@ -486,11 +439,6 @@ 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 => (
|
||||
@@ -581,25 +529,11 @@ 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(
|
||||
(value: IBuilderQuery['groupBy']) => {
|
||||
const groupBy = [];
|
||||
const newGroupBy = [];
|
||||
|
||||
for (let index = 0; index < value.length; index++) {
|
||||
const element = (value[index] as unknown) as string;
|
||||
@@ -609,17 +543,13 @@ function K8sClustersList({
|
||||
);
|
||||
|
||||
if (key) {
|
||||
groupBy.push(key);
|
||||
newGroupBy.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// 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),
|
||||
});
|
||||
setGroupBy(newGroupBy);
|
||||
setExpandedRowKeys([]);
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@@ -627,7 +557,7 @@ function K8sClustersList({
|
||||
category: InfraMonitoringEvents.Cluster,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
[groupByFiltersData, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag, Tooltip } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import {
|
||||
K8sClustersData,
|
||||
K8sClustersListPayload,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
@@ -15,11 +14,14 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -93,23 +95,21 @@ function DaemonSetDetails({
|
||||
: (selectedTime as Time),
|
||||
);
|
||||
|
||||
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 [selectedView, setSelectedView] = useInfraMonitoringView();
|
||||
const [logFiltersParam, setLogFiltersParam] = useInfraMonitoringLogFilters();
|
||||
const [
|
||||
tracesFiltersParam,
|
||||
setTracesFiltersParam,
|
||||
] = useInfraMonitoringTracesFilters();
|
||||
const [
|
||||
eventsFiltersParam,
|
||||
setEventsFiltersParam,
|
||||
] = useInfraMonitoringEventsFilters();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
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);
|
||||
const filters =
|
||||
selectedView === VIEW_TYPES.LOGS ? logFiltersParam : tracesFiltersParam;
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
@@ -143,16 +143,14 @@ function DaemonSetDetails({
|
||||
}, [
|
||||
daemonSet?.meta.k8s_daemonset_name,
|
||||
daemonSet?.meta.k8s_namespace_name,
|
||||
searchParams,
|
||||
selectedView,
|
||||
logFiltersParam,
|
||||
tracesFiltersParam,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
if (eventsFiltersParam) {
|
||||
return eventsFiltersParam;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
@@ -181,7 +179,7 @@ function DaemonSetDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [daemonSet?.meta.k8s_daemonset_name, searchParams]);
|
||||
}, [daemonSet?.meta.k8s_daemonset_name, eventsFiltersParam]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@@ -222,13 +220,9 @@ 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),
|
||||
});
|
||||
setLogFiltersParam(null);
|
||||
setTracesFiltersParam(null);
|
||||
setEventsFiltersParam(null);
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@@ -296,20 +290,17 @@ function DaemonSetDetails({
|
||||
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...(primaryFilters || []),
|
||||
...(newFilters || []),
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...(primaryFilters || []),
|
||||
...(newFilters || []),
|
||||
...(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,
|
||||
});
|
||||
setLogFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
return updatedFilters;
|
||||
});
|
||||
},
|
||||
@@ -337,21 +328,18 @@ function DaemonSetDetails({
|
||||
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...(primaryFilters || []),
|
||||
...(value?.items?.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_DAEMON_SET_NAME,
|
||||
) || []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...(primaryFilters || []),
|
||||
...(value?.items?.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_DAEMON_SET_NAME,
|
||||
) || []),
|
||||
].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,
|
||||
});
|
||||
setTracesFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -392,13 +380,8 @@ 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,
|
||||
});
|
||||
setEventsFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -28,12 +27,17 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import { GetK8sEntityToAggregateAttribute, K8sCategory } from '../constants';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from '../constants';
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringDaemonSetUID,
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from '../hooks';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -62,54 +66,25 @@ function K8sDaemonSetsList({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
|
||||
const [selectedDaemonSetUID, setSelectedDaemonSetUID] = useState<
|
||||
string | null
|
||||
>(() => {
|
||||
const daemonSetUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.DAEMONSET_UID,
|
||||
);
|
||||
if (daemonSetUID) {
|
||||
return daemonSetUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const [
|
||||
selectedDaemonSetUID,
|
||||
setSelectedDaemonSetUID,
|
||||
] = useInfraMonitoringDaemonSetUID();
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.DAEMONSETS);
|
||||
|
||||
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 [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
|
||||
const [, setView] = useInfraMonitoringView();
|
||||
const [, setTracesFilters] = useInfraMonitoringTracesFilters();
|
||||
const [, setEventsFilters] = useInfraMonitoringEventsFilters();
|
||||
const [, setLogFilters] = useInfraMonitoringLogFilters();
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@@ -136,7 +111,7 @@ function K8sDaemonSetsList({
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
}, [quickFiltersLastUpdated, setCurrentPage]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
@@ -189,25 +164,28 @@ function K8sDaemonSetsList({
|
||||
filters,
|
||||
start: Math.floor(minTime / 1000000),
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
orderBy: orderBy || baseQuery.orderBy,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||
|
||||
const groupedByRowDataQueryKey = useMemo(() => {
|
||||
// be careful with what you serialize from selectedRowData
|
||||
// since it's react node, it could contain circular references
|
||||
const selectedRowDataKey = JSON.stringify(selectedRowData?.groupedByMeta);
|
||||
if (selectedDaemonSetUID) {
|
||||
return [
|
||||
'daemonSetList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'daemonSetList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
String(minTime),
|
||||
String(maxTime),
|
||||
];
|
||||
@@ -376,26 +354,15 @@ function K8sDaemonSetsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
const currentOrderBy = {
|
||||
setOrderBy({
|
||||
columnName: sorter.field as string,
|
||||
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],
|
||||
[setCurrentPage, setOrderBy],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -460,10 +427,6 @@ function K8sDaemonSetsList({
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setSelectedDaemonSetUID(record.daemonsetUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.DAEMONSET_UID]: record.daemonsetUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@@ -492,11 +455,6 @@ 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 => (
|
||||
@@ -587,20 +545,10 @@ function K8sDaemonSetsList({
|
||||
|
||||
const handleCloseDaemonSetDetail = (): void => {
|
||||
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),
|
||||
),
|
||||
),
|
||||
});
|
||||
setView(null);
|
||||
setTracesFilters(null);
|
||||
setEventsFilters(null);
|
||||
setLogFilters(null);
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@@ -621,10 +569,6 @@ 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, {
|
||||
@@ -633,7 +577,7 @@ function K8sDaemonSetsList({
|
||||
category: InfraMonitoringEvents.DaemonSet,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
[groupByFiltersData, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag, Tooltip } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import {
|
||||
K8sDaemonSetsData,
|
||||
K8sDaemonSetsListPayload,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
@@ -16,15 +15,15 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
filterDuplicateFilters,
|
||||
getFiltersFromParams,
|
||||
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||
import {
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -97,23 +96,21 @@ function DeploymentDetails({
|
||||
: (selectedTime as Time),
|
||||
);
|
||||
|
||||
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 [selectedView, setSelectedView] = useInfraMonitoringView();
|
||||
const [logFiltersParam, setLogFiltersParam] = useInfraMonitoringLogFilters();
|
||||
const [
|
||||
tracesFiltersParam,
|
||||
setTracesFiltersParam,
|
||||
] = useInfraMonitoringTracesFilters();
|
||||
const [
|
||||
eventsFiltersParam,
|
||||
setEventsFiltersParam,
|
||||
] = useInfraMonitoringEventsFilters();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
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);
|
||||
const filters =
|
||||
selectedView === VIEW_TYPES.LOGS ? logFiltersParam : tracesFiltersParam;
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
@@ -147,16 +144,14 @@ function DeploymentDetails({
|
||||
}, [
|
||||
deployment?.meta.k8s_deployment_name,
|
||||
deployment?.meta.k8s_namespace_name,
|
||||
searchParams,
|
||||
selectedView,
|
||||
logFiltersParam,
|
||||
tracesFiltersParam,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
if (eventsFiltersParam) {
|
||||
return eventsFiltersParam;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
@@ -185,7 +180,7 @@ function DeploymentDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [deployment?.meta.k8s_deployment_name, searchParams]);
|
||||
}, [deployment?.meta.k8s_deployment_name, eventsFiltersParam]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@@ -226,13 +221,9 @@ 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),
|
||||
});
|
||||
setLogFiltersParam(null);
|
||||
setTracesFiltersParam(null);
|
||||
setEventsFiltersParam(null);
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@@ -309,13 +300,8 @@ function DeploymentDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setLogFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -354,13 +340,8 @@ function DeploymentDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setTracesFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -403,13 +384,8 @@ function DeploymentDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setEventsFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -28,12 +27,17 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import { GetK8sEntityToAggregateAttribute, K8sCategory } from '../constants';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from '../constants';
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringDeploymentUID,
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from '../hooks';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -62,55 +66,26 @@ function K8sDeploymentsList({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
|
||||
const [selectedDeploymentUID, setselectedDeploymentUID] = useState<
|
||||
string | null
|
||||
>(() => {
|
||||
const deploymentUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.DEPLOYMENT_UID,
|
||||
);
|
||||
if (deploymentUID) {
|
||||
return deploymentUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const [
|
||||
selectedDeploymentUID,
|
||||
setselectedDeploymentUID,
|
||||
] = useInfraMonitoringDeploymentUID();
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.DEPLOYMENTS);
|
||||
|
||||
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 [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
|
||||
const [, setView] = useInfraMonitoringView();
|
||||
const [, setTracesFilters] = useInfraMonitoringTracesFilters();
|
||||
const [, setEventsFilters] = useInfraMonitoringEventsFilters();
|
||||
const [, setLogFilters] = useInfraMonitoringLogFilters();
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@@ -137,7 +112,7 @@ function K8sDeploymentsList({
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
}, [quickFiltersLastUpdated, setCurrentPage]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
@@ -190,25 +165,28 @@ function K8sDeploymentsList({
|
||||
filters,
|
||||
start: Math.floor(minTime / 1000000),
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
orderBy: orderBy || baseQuery.orderBy,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||
|
||||
const groupedByRowDataQueryKey = useMemo(() => {
|
||||
// be careful with what you serialize from selectedRowData
|
||||
// since it's react node, it could contain circular references
|
||||
const selectedRowDataKey = JSON.stringify(selectedRowData?.groupedByMeta);
|
||||
if (selectedDeploymentUID) {
|
||||
return [
|
||||
'deploymentList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'deploymentList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
String(minTime),
|
||||
String(maxTime),
|
||||
];
|
||||
@@ -379,26 +357,15 @@ function K8sDeploymentsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
const currentOrderBy = {
|
||||
setOrderBy({
|
||||
columnName: sorter.field as string,
|
||||
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],
|
||||
[setCurrentPage, setOrderBy],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -466,10 +433,6 @@ 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);
|
||||
}
|
||||
@@ -498,11 +461,6 @@ 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 => (
|
||||
@@ -593,20 +551,10 @@ 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),
|
||||
),
|
||||
),
|
||||
});
|
||||
setView(null);
|
||||
setTracesFilters(null);
|
||||
setEventsFilters(null);
|
||||
setLogFilters(null);
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@@ -628,10 +576,6 @@ 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, {
|
||||
@@ -640,7 +584,7 @@ function K8sDeploymentsList({
|
||||
category: InfraMonitoringEvents.Deployment,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
[groupByFiltersData, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag, Tooltip } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import {
|
||||
K8sDeploymentsData,
|
||||
K8sDeploymentsListPayload,
|
||||
|
||||
@@ -147,9 +147,11 @@
|
||||
.ant-table-cell:nth-child(n + 3) {
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.column-header-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr > td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@@ -121,9 +121,11 @@
|
||||
.ant-table-cell:nth-child(n + 3) {
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.column-header-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr > td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@@ -102,6 +102,7 @@
|
||||
|
||||
.progress-container {
|
||||
width: 158px;
|
||||
|
||||
.ant-progress {
|
||||
margin: 0;
|
||||
|
||||
@@ -185,6 +186,7 @@
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-drawer-close {
|
||||
padding: 0px;
|
||||
}
|
||||
@@ -369,9 +371,11 @@
|
||||
.ant-table-cell:nth-child(n + 3) {
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.column-header-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ant-table-tbody > tr > td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { VerticalAlignTopOutlined } from '@ant-design/icons';
|
||||
import * as Sentry from '@sentry/react';
|
||||
import type { CollapseProps } from 'antd';
|
||||
@@ -37,11 +36,11 @@ import {
|
||||
GetPodsQuickFiltersConfig,
|
||||
GetStatefulsetsQuickFiltersConfig,
|
||||
GetVolumesQuickFiltersConfig,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategories,
|
||||
} from './constants';
|
||||
import K8sDaemonSetsList from './DaemonSets/K8sDaemonSetsList';
|
||||
import K8sDeploymentsList from './Deployments/K8sDeploymentsList';
|
||||
import { useInfraMonitoringCategory, useInfraMonitoringFilters } from './hooks';
|
||||
import K8sJobsList from './Jobs/K8sJobsList';
|
||||
import K8sNamespacesList from './Namespaces/K8sNamespacesList';
|
||||
import K8sNodesList from './Nodes/K8sNodesList';
|
||||
@@ -54,14 +53,8 @@ import './InfraMonitoringK8s.styles.scss';
|
||||
export default function InfraMonitoringK8s(): JSX.Element {
|
||||
const [showFilters, setShowFilters] = useState(true);
|
||||
|
||||
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 [selectedCategory, setSelectedCategory] = useInfraMonitoringCategory();
|
||||
const [, setFilters] = useInfraMonitoringFilters();
|
||||
const [quickFiltersLastUpdated, setQuickFiltersLastUpdated] = useState(-1);
|
||||
|
||||
const { currentQuery } = useQueryBuilder();
|
||||
@@ -86,12 +79,7 @@ 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,
|
||||
),
|
||||
});
|
||||
setFilters(JSON.stringify(query.builder.queryData[0].filters));
|
||||
|
||||
logEvent(InfraMonitoringEvents.FilterApplied, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@@ -317,10 +305,8 @@ 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
|
||||
setFilters(null);
|
||||
handleChangeQueryData('filters', { items: [], op: 'and' });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
@@ -15,11 +14,14 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -90,23 +92,21 @@ function JobDetails({
|
||||
: (selectedTime as Time),
|
||||
);
|
||||
|
||||
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 [selectedView, setSelectedView] = useInfraMonitoringView();
|
||||
const [logFiltersParam, setLogFiltersParam] = useInfraMonitoringLogFilters();
|
||||
const [
|
||||
tracesFiltersParam,
|
||||
setTracesFiltersParam,
|
||||
] = useInfraMonitoringTracesFilters();
|
||||
const [
|
||||
eventsFiltersParam,
|
||||
setEventsFiltersParam,
|
||||
] = useInfraMonitoringEventsFilters();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
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);
|
||||
const filters =
|
||||
selectedView === VIEW_TYPES.LOGS ? logFiltersParam : tracesFiltersParam;
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
@@ -137,15 +137,17 @@ function JobDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [job?.meta.k8s_job_name, job?.meta.k8s_namespace_name, searchParams]);
|
||||
}, [
|
||||
job?.meta.k8s_job_name,
|
||||
job?.meta.k8s_namespace_name,
|
||||
selectedView,
|
||||
logFiltersParam,
|
||||
tracesFiltersParam,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
if (eventsFiltersParam) {
|
||||
return eventsFiltersParam;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
@@ -174,7 +176,7 @@ function JobDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [job?.meta.k8s_job_name, searchParams]);
|
||||
}, [job?.meta.k8s_job_name, eventsFiltersParam]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@@ -215,13 +217,9 @@ 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),
|
||||
});
|
||||
setLogFiltersParam(null);
|
||||
setTracesFiltersParam(null);
|
||||
setEventsFiltersParam(null);
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@@ -288,20 +286,17 @@ function JobDetails({
|
||||
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...(primaryFilters || []),
|
||||
...(newFilters || []),
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...(primaryFilters || []),
|
||||
...(newFilters || []),
|
||||
...(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,
|
||||
});
|
||||
setLogFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -330,21 +325,18 @@ function JobDetails({
|
||||
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...(primaryFilters || []),
|
||||
...(value?.items?.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_JOB_NAME,
|
||||
) || []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...(primaryFilters || []),
|
||||
...(value?.items?.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_JOB_NAME,
|
||||
) || []),
|
||||
].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,
|
||||
});
|
||||
setTracesFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -385,13 +377,8 @@ 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,
|
||||
});
|
||||
setEventsFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -28,12 +27,17 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import { GetK8sEntityToAggregateAttribute, K8sCategory } from '../constants';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from '../constants';
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringJobUID,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from '../hooks';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -61,51 +65,24 @@ function K8sJobsList({
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
|
||||
const [selectedJobUID, setselectedJobUID] = useState<string | null>(() => {
|
||||
const jobUID = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.JOB_UID);
|
||||
if (jobUID) {
|
||||
return jobUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const [selectedJobUID, setselectedJobUID] = useInfraMonitoringJobUID();
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.JOBS);
|
||||
|
||||
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 [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
|
||||
const [, setView] = useInfraMonitoringView();
|
||||
const [, setTracesFilters] = useInfraMonitoringTracesFilters();
|
||||
const [, setEventsFilters] = useInfraMonitoringEventsFilters();
|
||||
const [, setLogFilters] = useInfraMonitoringLogFilters();
|
||||
|
||||
const [selectedRowData, setSelectedRowData] = useState<K8sJobsRowData | null>(
|
||||
null,
|
||||
@@ -131,7 +108,7 @@ function K8sJobsList({
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
}, [quickFiltersLastUpdated, setCurrentPage]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
@@ -184,25 +161,28 @@ function K8sJobsList({
|
||||
filters,
|
||||
start: Math.floor(minTime / 1000000),
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
orderBy: orderBy || baseQuery.orderBy,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||
|
||||
const groupedByRowDataQueryKey = useMemo(() => {
|
||||
// be careful with what you serialize from selectedRowData
|
||||
// since it's react node, it could contain circular references
|
||||
const selectedRowDataKey = JSON.stringify(selectedRowData?.groupedByMeta);
|
||||
if (selectedJobUID) {
|
||||
return [
|
||||
'jobList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'jobList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
String(minTime),
|
||||
String(maxTime),
|
||||
];
|
||||
@@ -360,26 +340,15 @@ function K8sJobsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
const currentOrderBy = {
|
||||
setOrderBy({
|
||||
columnName: sorter.field as string,
|
||||
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],
|
||||
[setCurrentPage, setOrderBy],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -431,10 +400,6 @@ 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);
|
||||
}
|
||||
@@ -463,11 +428,6 @@ 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 => (
|
||||
@@ -558,20 +518,10 @@ 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),
|
||||
),
|
||||
),
|
||||
});
|
||||
setView(null);
|
||||
setTracesFilters(null);
|
||||
setEventsFilters(null);
|
||||
setLogFilters(null);
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@@ -593,10 +543,6 @@ 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,
|
||||
@@ -604,7 +550,7 @@ function K8sJobsList({
|
||||
category: InfraMonitoringEvents.Job,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
[groupByFiltersData, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag, Tooltip } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import {
|
||||
K8sJobsData,
|
||||
K8sJobsListPayload,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Button, Select } from 'antd';
|
||||
import { initialQueriesMap } from 'constants/queryBuilder';
|
||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
||||
@@ -10,7 +9,8 @@ import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteRe
|
||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { INFRA_MONITORING_K8S_PARAMS_KEYS, K8sCategory } from './constants';
|
||||
import { K8sCategory } from './constants';
|
||||
import { useInfraMonitoringFiltersK8s } from './hooks';
|
||||
import K8sFiltersSidePanel from './K8sFiltersSidePanel/K8sFiltersSidePanel';
|
||||
import { IEntityColumn } from './utils';
|
||||
|
||||
@@ -50,17 +50,14 @@ function K8sHeader({
|
||||
showAutoRefresh,
|
||||
}: K8sHeaderProps): JSX.Element {
|
||||
const [isFiltersSidePanelOpen, setIsFiltersSidePanelOpen] = useState(false);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [urlFilters, setUrlFilters] = useInfraMonitoringFiltersK8s();
|
||||
|
||||
const currentQuery = initialQueriesMap[DataSource.METRICS];
|
||||
|
||||
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;
|
||||
filters = urlFilters;
|
||||
}
|
||||
return {
|
||||
...currentQuery,
|
||||
@@ -78,19 +75,16 @@ function K8sHeader({
|
||||
],
|
||||
},
|
||||
};
|
||||
}, [currentQuery, searchParams]);
|
||||
}, [currentQuery, urlFilters]);
|
||||
|
||||
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),
|
||||
});
|
||||
setUrlFilters(value || null);
|
||||
},
|
||||
[handleFiltersChange, searchParams, setSearchParams],
|
||||
[handleFiltersChange, setUrlFilters],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -27,12 +26,17 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import { GetK8sEntityToAggregateAttribute, K8sCategory } from '../constants';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from '../constants';
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringNamespaceUID,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from '../hooks';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -62,53 +66,25 @@ function K8sNamespacesList({
|
||||
);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
|
||||
const [selectedNamespaceUID, setselectedNamespaceUID] = useState<
|
||||
string | null
|
||||
>(() => {
|
||||
const namespaceUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.NAMESPACE_UID,
|
||||
);
|
||||
if (namespaceUID) {
|
||||
return namespaceUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const [
|
||||
selectedNamespaceUID,
|
||||
setselectedNamespaceUID,
|
||||
] = useInfraMonitoringNamespaceUID();
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.NAMESPACES);
|
||||
|
||||
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 [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
|
||||
const [, setView] = useInfraMonitoringView();
|
||||
const [, setTracesFilters] = useInfraMonitoringTracesFilters();
|
||||
const [, setEventsFilters] = useInfraMonitoringEventsFilters();
|
||||
const [, setLogFilters] = useInfraMonitoringLogFilters();
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@@ -135,7 +111,7 @@ function K8sNamespacesList({
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
}, [quickFiltersLastUpdated, setCurrentPage]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
@@ -188,25 +164,28 @@ function K8sNamespacesList({
|
||||
filters,
|
||||
start: Math.floor(minTime / 1000000),
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
orderBy: orderBy || baseQuery.orderBy,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||
|
||||
const groupedByRowDataQueryKey = useMemo(() => {
|
||||
// be careful with what you serialize from selectedRowData
|
||||
// since it's react node, it could contain circular references
|
||||
const selectedRowDataKey = JSON.stringify(selectedRowData?.groupedByMeta);
|
||||
if (selectedNamespaceUID) {
|
||||
return [
|
||||
'namespaceList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'namespaceList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
String(minTime),
|
||||
String(maxTime),
|
||||
];
|
||||
@@ -375,26 +354,15 @@ function K8sNamespacesList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
const currentOrderBy = {
|
||||
setOrderBy({
|
||||
columnName: sorter.field as string,
|
||||
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],
|
||||
[setCurrentPage, setOrderBy],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -462,10 +430,6 @@ 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);
|
||||
}
|
||||
@@ -494,11 +458,6 @@ 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 => (
|
||||
@@ -589,20 +548,10 @@ 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),
|
||||
),
|
||||
),
|
||||
});
|
||||
setView(null);
|
||||
setTracesFilters(null);
|
||||
setEventsFilters(null);
|
||||
setLogFilters(null);
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@@ -625,10 +574,6 @@ 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,
|
||||
@@ -636,7 +581,7 @@ function K8sNamespacesList({
|
||||
category: InfraMonitoringEvents.Namespace,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
[groupByFiltersData, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
|
||||
.progress-container {
|
||||
width: 158px;
|
||||
|
||||
.ant-progress {
|
||||
margin: 0;
|
||||
|
||||
@@ -184,6 +185,7 @@
|
||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-drawer-close {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
@@ -16,12 +15,15 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||
import {
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -94,23 +96,21 @@ function NamespaceDetails({
|
||||
: (selectedTime as Time),
|
||||
);
|
||||
|
||||
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 [selectedView, setSelectedView] = useInfraMonitoringView();
|
||||
const [logFiltersParam, setLogFiltersParam] = useInfraMonitoringLogFilters();
|
||||
const [
|
||||
tracesFiltersParam,
|
||||
setTracesFiltersParam,
|
||||
] = useInfraMonitoringTracesFilters();
|
||||
const [
|
||||
eventsFiltersParam,
|
||||
setEventsFiltersParam,
|
||||
] = useInfraMonitoringEventsFilters();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
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);
|
||||
const filters =
|
||||
selectedView === VIEW_TYPES.LOGS ? logFiltersParam : tracesFiltersParam;
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
@@ -130,15 +130,16 @@ function NamespaceDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [namespace?.namespaceName, searchParams]);
|
||||
}, [
|
||||
namespace?.namespaceName,
|
||||
selectedView,
|
||||
logFiltersParam,
|
||||
tracesFiltersParam,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
if (eventsFiltersParam) {
|
||||
return eventsFiltersParam;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
@@ -167,7 +168,7 @@ function NamespaceDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [namespace?.namespaceName, searchParams]);
|
||||
}, [namespace?.namespaceName, eventsFiltersParam]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@@ -208,13 +209,9 @@ 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),
|
||||
});
|
||||
setLogFiltersParam(null);
|
||||
setTracesFiltersParam(null);
|
||||
setEventsFiltersParam(null);
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@@ -281,21 +278,17 @@ function NamespaceDetails({
|
||||
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...(primaryFilters || []),
|
||||
...(newFilters || []),
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...(primaryFilters || []),
|
||||
...(newFilters || []),
|
||||
...(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,
|
||||
});
|
||||
setLogFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -324,21 +317,18 @@ function NamespaceDetails({
|
||||
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...(primaryFilters || []),
|
||||
...(value?.items?.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||
) || []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...(primaryFilters || []),
|
||||
...(value?.items?.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_NAMESPACE_NAME,
|
||||
) || []),
|
||||
].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,
|
||||
),
|
||||
});
|
||||
setTracesFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -379,13 +369,8 @@ 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,
|
||||
),
|
||||
});
|
||||
setEventsFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { TableColumnType as ColumnType, Tag } from 'antd';
|
||||
import {
|
||||
K8sNamespacesData,
|
||||
K8sNamespacesListPayload,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -27,12 +26,17 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import { GetK8sEntityToAggregateAttribute, K8sCategory } from '../constants';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from '../constants';
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringNodeUID,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from '../hooks';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -60,50 +64,24 @@ function K8sNodesList({
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams, false));
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
|
||||
const [selectedNodeUID, setSelectedNodeUID] = useState<string | null>(() => {
|
||||
const nodeUID = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.NODE_UID);
|
||||
if (nodeUID) {
|
||||
return nodeUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const [selectedNodeUID, setSelectedNodeUID] = useInfraMonitoringNodeUID();
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.NODES);
|
||||
|
||||
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 [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
|
||||
// These params are used only for clearing in handleCloseNodeDetail
|
||||
const [, setView] = useInfraMonitoringView();
|
||||
const [, setTracesFilters] = useInfraMonitoringTracesFilters();
|
||||
const [, setEventsFilters] = useInfraMonitoringEventsFilters();
|
||||
const [, setLogFilters] = useInfraMonitoringLogFilters();
|
||||
|
||||
const [selectedRowData, setSelectedRowData] = useState<K8sNodesRowData | null>(
|
||||
null,
|
||||
@@ -129,7 +107,7 @@ function K8sNodesList({
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
}, [quickFiltersLastUpdated, setCurrentPage]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
@@ -182,25 +160,28 @@ function K8sNodesList({
|
||||
filters,
|
||||
start: Math.floor(minTime / 1000000),
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
orderBy: orderBy || baseQuery.orderBy,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||
|
||||
const groupedByRowDataQueryKey = useMemo(() => {
|
||||
// be careful with what you serialize from selectedRowData
|
||||
// since it's react node, it could contain circular references
|
||||
const selectedRowDataKey = JSON.stringify(selectedRowData?.groupedByMeta);
|
||||
if (selectedNodeUID) {
|
||||
return [
|
||||
'nodeList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'nodeList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
String(minTime),
|
||||
String(maxTime),
|
||||
];
|
||||
@@ -365,26 +346,15 @@ function K8sNodesList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
const currentOrderBy = {
|
||||
setOrderBy({
|
||||
columnName: sorter.field as string,
|
||||
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],
|
||||
[setCurrentPage, setOrderBy],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -441,10 +411,6 @@ function K8sNodesList({
|
||||
if (groupBy.length === 0) {
|
||||
setSelectedRowData(null);
|
||||
setSelectedNodeUID(record.nodeUID);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.NODE_UID]: record.nodeUID,
|
||||
});
|
||||
} else {
|
||||
handleGroupByRowClick(record);
|
||||
}
|
||||
@@ -473,11 +439,6 @@ 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 => (
|
||||
@@ -569,45 +530,31 @@ function K8sNodesList({
|
||||
|
||||
const handleCloseNodeDetail = (): void => {
|
||||
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),
|
||||
),
|
||||
),
|
||||
});
|
||||
setView(null);
|
||||
setTracesFilters(null);
|
||||
setEventsFilters(null);
|
||||
setLogFilters(null);
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
(value: IBuilderQuery['groupBy']) => {
|
||||
const groupBy = [];
|
||||
const newGroupBy = [];
|
||||
|
||||
for (let index = 0; index < value.length; index++) {
|
||||
const element = (value[index] as unknown) as string;
|
||||
|
||||
const key = groupByFiltersData?.payload?.attributeKeys?.find(
|
||||
(key) => key.key === element,
|
||||
(k) => k.key === element,
|
||||
);
|
||||
|
||||
if (key) {
|
||||
groupBy.push(key);
|
||||
newGroupBy.push(key);
|
||||
}
|
||||
}
|
||||
// Reset pagination on switching to groupBy
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setGroupBy(newGroupBy);
|
||||
setExpandedRowKeys([]);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@@ -615,7 +562,7 @@ function K8sNodesList({
|
||||
category: InfraMonitoringEvents.Node,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
[groupByFiltersData, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
@@ -16,15 +15,15 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
filterDuplicateFilters,
|
||||
getFiltersFromParams,
|
||||
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import NodeEvents from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityEvents';
|
||||
import {
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -94,23 +93,21 @@ function NodeDetails({
|
||||
: (selectedTime as Time),
|
||||
);
|
||||
|
||||
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 [selectedView, setSelectedView] = useInfraMonitoringView();
|
||||
const [logFiltersParam, setLogFiltersParam] = useInfraMonitoringLogFilters();
|
||||
const [
|
||||
tracesFiltersParam,
|
||||
setTracesFiltersParam,
|
||||
] = useInfraMonitoringTracesFilters();
|
||||
const [
|
||||
eventsFiltersParam,
|
||||
setEventsFiltersParam,
|
||||
] = useInfraMonitoringEventsFilters();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
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);
|
||||
const filters =
|
||||
selectedView === VIEW_TYPES.LOGS ? logFiltersParam : tracesFiltersParam;
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
@@ -130,15 +127,16 @@ function NodeDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [node?.meta.k8s_node_name, searchParams]);
|
||||
}, [
|
||||
node?.meta.k8s_node_name,
|
||||
selectedView,
|
||||
logFiltersParam,
|
||||
tracesFiltersParam,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
if (eventsFiltersParam) {
|
||||
return eventsFiltersParam;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
@@ -167,7 +165,7 @@ function NodeDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [node?.meta.k8s_node_name, searchParams]);
|
||||
}, [node?.meta.k8s_node_name, eventsFiltersParam]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@@ -208,13 +206,9 @@ 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),
|
||||
});
|
||||
setLogFiltersParam(null);
|
||||
setTracesFiltersParam(null);
|
||||
setEventsFiltersParam(null);
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@@ -290,13 +284,8 @@ function NodeDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setLogFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -335,13 +324,8 @@ function NodeDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setTracesFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -382,13 +366,8 @@ 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,
|
||||
});
|
||||
setEventsFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag, Tooltip } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import {
|
||||
K8sNodesData,
|
||||
K8sNodesListPayload,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -30,12 +29,17 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import { GetK8sEntityToAggregateAttribute, K8sCategory } from '../constants';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from '../constants';
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringPodUID,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from '../hooks';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import {
|
||||
@@ -64,41 +68,25 @@ function K8sPodsList({
|
||||
const { maxTime, minTime } = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
const [defaultOrderBy] = useState(orderBy);
|
||||
const [selectedPodUID, setSelectedPodUID] = useInfraMonitoringPodUID();
|
||||
const [, setView] = useInfraMonitoringView();
|
||||
const [, setTracesFilters] = useInfraMonitoringTracesFilters();
|
||||
const [, setEventsFilters] = useInfraMonitoringEventsFilters();
|
||||
const [, setLogFilters] = useInfraMonitoringLogFilters();
|
||||
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [addedColumns, setAddedColumns] = useState<IEntityColumn[]>([]);
|
||||
|
||||
const [availableColumns, setAvailableColumns] = useState<IEntityColumn[]>(
|
||||
defaultAvailableColumns,
|
||||
);
|
||||
|
||||
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,
|
||||
);
|
||||
@@ -151,7 +139,7 @@ function K8sPodsList({
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
}, [quickFiltersLastUpdated, setCurrentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
const addedColumns = JSON.parse(get('k8sPodsAddedColumns') ?? '[]');
|
||||
@@ -170,19 +158,6 @@ function K8sPodsList({
|
||||
}
|
||||
}, []);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams, false));
|
||||
|
||||
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);
|
||||
|
||||
const query = useMemo(() => {
|
||||
@@ -195,7 +170,7 @@ function K8sPodsList({
|
||||
filters: queryFilters,
|
||||
start: Math.floor(minTime / 1000000),
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
orderBy: orderBy || baseQuery.orderBy,
|
||||
};
|
||||
|
||||
if (groupBy.length > 0) {
|
||||
@@ -299,19 +274,22 @@ function K8sPodsList({
|
||||
}, [minTime, maxTime, orderBy, selectedRowData]);
|
||||
|
||||
const groupedByRowDataQueryKey = useMemo(() => {
|
||||
// be careful with what you serialize from selectedRowData
|
||||
// since it's react node, it could contain circular references
|
||||
const selectedRowDataKey = JSON.stringify(selectedRowData?.groupedByMeta);
|
||||
if (selectedPodUID) {
|
||||
return [
|
||||
'podList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'podList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
String(minTime),
|
||||
String(maxTime),
|
||||
];
|
||||
@@ -354,10 +332,10 @@ function K8sPodsList({
|
||||
[groupedByRowData, groupBy],
|
||||
);
|
||||
|
||||
const columns = useMemo(() => getK8sPodsListColumns(addedColumns, groupBy), [
|
||||
addedColumns,
|
||||
groupBy,
|
||||
]);
|
||||
const columns = useMemo(
|
||||
() => getK8sPodsListColumns(addedColumns, groupBy, defaultOrderBy),
|
||||
[addedColumns, groupBy, defaultOrderBy],
|
||||
);
|
||||
|
||||
const handleTableChange: TableProps<K8sPodsRowData>['onChange'] = useCallback(
|
||||
(
|
||||
@@ -375,26 +353,15 @@ function K8sPodsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
const currentOrderBy = {
|
||||
setOrderBy({
|
||||
columnName: sorter.field as string,
|
||||
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],
|
||||
[setCurrentPage, setOrderBy],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -426,28 +393,24 @@ function K8sPodsList({
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
(value: IBuilderQuery['groupBy']) => {
|
||||
const groupBy = [];
|
||||
const newGroupBy = [];
|
||||
|
||||
for (let index = 0; index < value.length; index++) {
|
||||
const element = (value[index] as unknown) as string;
|
||||
|
||||
const key = groupByFiltersData?.payload?.attributeKeys?.find(
|
||||
(key) => key.key === element,
|
||||
(k) => k.key === element,
|
||||
);
|
||||
|
||||
if (key) {
|
||||
groupBy.push(key);
|
||||
newGroupBy.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset pagination on switching to groupBy
|
||||
setCurrentPage(1);
|
||||
setGroupBy(groupBy);
|
||||
setGroupBy(newGroupBy);
|
||||
setExpandedRowKeys([]);
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY]: JSON.stringify(groupBy),
|
||||
});
|
||||
|
||||
logEvent(InfraMonitoringEvents.GroupByChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
@@ -455,7 +418,7 @@ function K8sPodsList({
|
||||
category: InfraMonitoringEvents.Pod,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
[groupByFiltersData, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -498,10 +461,6 @@ 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);
|
||||
@@ -516,20 +475,10 @@ 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),
|
||||
),
|
||||
),
|
||||
});
|
||||
setView(null);
|
||||
setTracesFilters(null);
|
||||
setEventsFilters(null);
|
||||
setLogFilters(null);
|
||||
};
|
||||
|
||||
const handleAddColumn = useCallback(
|
||||
@@ -568,9 +517,10 @@ function K8sPodsList({
|
||||
[setAddedColumns, setAvailableColumns],
|
||||
);
|
||||
|
||||
const nestedColumns = useMemo(() => getK8sPodsListColumns(addedColumns, []), [
|
||||
addedColumns,
|
||||
]);
|
||||
const nestedColumns = useMemo(
|
||||
() => getK8sPodsListColumns(addedColumns, [], defaultOrderBy),
|
||||
[addedColumns, defaultOrderBy],
|
||||
);
|
||||
|
||||
const isGroupedByAttribute = groupBy.length > 0;
|
||||
|
||||
@@ -587,11 +537,6 @@ 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 => (
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
@@ -16,15 +15,15 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import {
|
||||
filterDuplicateFilters,
|
||||
getFiltersFromParams,
|
||||
} from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
|
||||
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||
import {
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -96,23 +95,21 @@ function PodDetails({
|
||||
: (selectedTime as Time),
|
||||
);
|
||||
|
||||
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 [selectedView, setSelectedView] = useInfraMonitoringView();
|
||||
const [logFiltersParam, setLogFiltersParam] = useInfraMonitoringLogFilters();
|
||||
const [
|
||||
tracesFiltersParam,
|
||||
setTracesFiltersParam,
|
||||
] = useInfraMonitoringTracesFilters();
|
||||
const [
|
||||
eventsFiltersParam,
|
||||
setEventsFiltersParam,
|
||||
] = useInfraMonitoringEventsFilters();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
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);
|
||||
const filters =
|
||||
selectedView === VIEW_TYPES.LOGS ? logFiltersParam : tracesFiltersParam;
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
@@ -143,15 +140,17 @@ function PodDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [pod?.meta.k8s_namespace_name, pod?.meta.k8s_pod_name, searchParams]);
|
||||
}, [
|
||||
pod?.meta.k8s_namespace_name,
|
||||
pod?.meta.k8s_pod_name,
|
||||
selectedView,
|
||||
logFiltersParam,
|
||||
tracesFiltersParam,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
if (eventsFiltersParam) {
|
||||
return eventsFiltersParam;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
@@ -180,7 +179,7 @@ function PodDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [pod?.meta.k8s_pod_name, searchParams]);
|
||||
}, [pod?.meta.k8s_pod_name, eventsFiltersParam]);
|
||||
|
||||
const [logsAndTracesFilters, setLogsAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@@ -221,13 +220,9 @@ 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),
|
||||
});
|
||||
setLogFiltersParam(null);
|
||||
setTracesFiltersParam(null);
|
||||
setEventsFiltersParam(null);
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@@ -305,13 +300,8 @@ function PodDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setLogFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -352,13 +342,8 @@ function PodDetails({
|
||||
),
|
||||
};
|
||||
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
|
||||
updatedFilters,
|
||||
),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
|
||||
});
|
||||
setTracesFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -399,13 +384,8 @@ 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,
|
||||
});
|
||||
setEventsFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -28,12 +27,17 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import { GetK8sEntityToAggregateAttribute, K8sCategory } from '../constants';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from '../constants';
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringStatefulSetUID,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from '../hooks';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -48,6 +52,7 @@ import {
|
||||
|
||||
import '../InfraMonitoringK8s.styles.scss';
|
||||
import './K8sStatefulSetsList.styles.scss';
|
||||
|
||||
function K8sStatefulSetsList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
@@ -61,55 +66,26 @@ function K8sStatefulSetsList({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
|
||||
const [selectedStatefulSetUID, setselectedStatefulSetUID] = useState<
|
||||
string | null
|
||||
>(() => {
|
||||
const statefulSetUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.STATEFULSET_UID,
|
||||
);
|
||||
if (statefulSetUID) {
|
||||
return statefulSetUID;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const [
|
||||
selectedStatefulSetUID,
|
||||
setselectedStatefulSetUID,
|
||||
] = useInfraMonitoringStatefulSetUID();
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.STATEFULSETS);
|
||||
|
||||
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 [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
|
||||
const [, setView] = useInfraMonitoringView();
|
||||
const [, setTracesFilters] = useInfraMonitoringTracesFilters();
|
||||
const [, setEventsFilters] = useInfraMonitoringEventsFilters();
|
||||
const [, setLogFilters] = useInfraMonitoringLogFilters();
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@@ -136,7 +112,7 @@ function K8sStatefulSetsList({
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
}, [quickFiltersLastUpdated, setCurrentPage]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
@@ -189,25 +165,28 @@ function K8sStatefulSetsList({
|
||||
filters,
|
||||
start: Math.floor(minTime / 1000000),
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
orderBy: orderBy || baseQuery.orderBy,
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [minTime, maxTime, orderBy, selectedRowData, groupBy]);
|
||||
|
||||
const groupedByRowDataQueryKey = useMemo(() => {
|
||||
// be careful with what you serialize from selectedRowData
|
||||
// since it's react node, it could contain circular references
|
||||
const selectedRowDataKey = JSON.stringify(selectedRowData?.groupedByMeta);
|
||||
if (selectedStatefulSetUID) {
|
||||
return [
|
||||
'statefulSetList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
];
|
||||
}
|
||||
return [
|
||||
'statefulSetList',
|
||||
JSON.stringify(queryFilters),
|
||||
JSON.stringify(orderBy),
|
||||
JSON.stringify(selectedRowData),
|
||||
selectedRowDataKey,
|
||||
String(minTime),
|
||||
String(maxTime),
|
||||
];
|
||||
@@ -378,26 +357,15 @@ function K8sStatefulSetsList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
const currentOrderBy = {
|
||||
setOrderBy({
|
||||
columnName: sorter.field as string,
|
||||
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],
|
||||
[setCurrentPage, setOrderBy],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -463,10 +431,6 @@ 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);
|
||||
}
|
||||
@@ -495,11 +459,6 @@ 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 => (
|
||||
@@ -590,20 +549,10 @@ 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),
|
||||
),
|
||||
),
|
||||
});
|
||||
setView(null);
|
||||
setTracesFilters(null);
|
||||
setEventsFilters(null);
|
||||
setLogFilters(null);
|
||||
};
|
||||
|
||||
const handleGroupByChange = useCallback(
|
||||
@@ -625,10 +574,6 @@ 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,
|
||||
@@ -636,7 +581,7 @@ function K8sStatefulSetsList({
|
||||
category: InfraMonitoringEvents.StatefulSet,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData, searchParams, setSearchParams],
|
||||
[groupByFiltersData, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
||||
import { Button, Divider, Drawer, Radio, Tooltip, Typography } from 'antd';
|
||||
import type { RadioChangeEvent } from 'antd/lib';
|
||||
@@ -15,16 +14,19 @@ import {
|
||||
initialQueryState,
|
||||
} from 'constants/queryBuilder';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import {
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from 'container/InfraMonitoringK8s/constants';
|
||||
import { filterDuplicateFilters } from 'container/InfraMonitoringK8s/commonUtils';
|
||||
import { 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';
|
||||
import EntityTraces from 'container/InfraMonitoringK8s/EntityDetailsUtils/EntityTraces';
|
||||
import { QUERY_KEYS } from 'container/InfraMonitoringK8s/EntityDetailsUtils/utils';
|
||||
import {
|
||||
useInfraMonitoringEventsFilters,
|
||||
useInfraMonitoringLogFilters,
|
||||
useInfraMonitoringTracesFilters,
|
||||
useInfraMonitoringView,
|
||||
} from 'container/InfraMonitoringK8s/hooks';
|
||||
import {
|
||||
CustomTimeType,
|
||||
Time,
|
||||
@@ -93,23 +95,21 @@ function StatefulSetDetails({
|
||||
: (selectedTime as Time),
|
||||
);
|
||||
|
||||
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 [selectedView, setSelectedView] = useInfraMonitoringView();
|
||||
const [logFiltersParam, setLogFiltersParam] = useInfraMonitoringLogFilters();
|
||||
const [
|
||||
tracesFiltersParam,
|
||||
setTracesFiltersParam,
|
||||
] = useInfraMonitoringTracesFilters();
|
||||
const [
|
||||
eventsFiltersParam,
|
||||
setEventsFiltersParam,
|
||||
] = useInfraMonitoringEventsFilters();
|
||||
const isDarkMode = useIsDarkMode();
|
||||
|
||||
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);
|
||||
const filters =
|
||||
selectedView === VIEW_TYPES.LOGS ? logFiltersParam : tracesFiltersParam;
|
||||
if (filters) {
|
||||
return filters;
|
||||
}
|
||||
@@ -141,18 +141,16 @@ function StatefulSetDetails({
|
||||
],
|
||||
};
|
||||
}, [
|
||||
searchParams,
|
||||
statefulSet?.meta.k8s_statefulset_name,
|
||||
statefulSet?.meta.k8s_namespace_name,
|
||||
selectedView,
|
||||
logFiltersParam,
|
||||
tracesFiltersParam,
|
||||
]);
|
||||
|
||||
const initialEventsFilters = useMemo(() => {
|
||||
const filters = getFiltersFromParams(
|
||||
searchParams,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
);
|
||||
if (filters) {
|
||||
return filters;
|
||||
if (eventsFiltersParam) {
|
||||
return eventsFiltersParam;
|
||||
}
|
||||
return {
|
||||
op: 'AND',
|
||||
@@ -181,7 +179,7 @@ function StatefulSetDetails({
|
||||
},
|
||||
],
|
||||
};
|
||||
}, [searchParams, statefulSet?.meta.k8s_statefulset_name]);
|
||||
}, [statefulSet?.meta.k8s_statefulset_name, eventsFiltersParam]);
|
||||
|
||||
const [logAndTracesFilters, setLogAndTracesFilters] = useState<
|
||||
IBuilderQuery['filters']
|
||||
@@ -222,13 +220,9 @@ 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),
|
||||
});
|
||||
setLogFiltersParam(null);
|
||||
setTracesFiltersParam(null);
|
||||
setEventsFiltersParam(null);
|
||||
logEvent(InfraMonitoringEvents.TabChanged, {
|
||||
entity: InfraMonitoringEvents.K8sEntity,
|
||||
page: InfraMonitoringEvents.DetailedPage,
|
||||
@@ -296,20 +290,17 @@ function StatefulSetDetails({
|
||||
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...(primaryFilters || []),
|
||||
...(newFilters || []),
|
||||
...(paginationFilter ? [paginationFilter] : []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...(primaryFilters || []),
|
||||
...(newFilters || []),
|
||||
...(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,
|
||||
});
|
||||
setLogFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -338,21 +329,18 @@ function StatefulSetDetails({
|
||||
|
||||
const updatedFilters = {
|
||||
op: 'AND',
|
||||
items: [
|
||||
...(primaryFilters || []),
|
||||
...(value?.items?.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_STATEFUL_SET_NAME,
|
||||
) || []),
|
||||
].filter((item): item is TagFilterItem => item !== undefined),
|
||||
items: filterDuplicateFilters(
|
||||
[
|
||||
...(primaryFilters || []),
|
||||
...(value?.items?.filter(
|
||||
(item) => item.key?.key !== QUERY_KEYS.K8S_STATEFUL_SET_NAME,
|
||||
) || []),
|
||||
].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,
|
||||
});
|
||||
setTracesFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
@@ -393,13 +381,8 @@ 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,
|
||||
});
|
||||
setEventsFiltersParam(updatedFilters);
|
||||
setSelectedView(view);
|
||||
|
||||
return updatedFilters;
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag, Tooltip } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import {
|
||||
K8sStatefulSetsData,
|
||||
K8sStatefulSetsListPayload,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useSearchParams } from 'react-router-dom-v5-compat';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
@@ -28,12 +27,13 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
import { FeatureKeys } from '../../../constants/features';
|
||||
import { useAppContext } from '../../../providers/App/App';
|
||||
import { getOrderByFromParams } from '../commonUtils';
|
||||
import { GetK8sEntityToAggregateAttribute, K8sCategory } from '../constants';
|
||||
import {
|
||||
GetK8sEntityToAggregateAttribute,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from '../constants';
|
||||
useInfraMonitoringCurrentPage,
|
||||
useInfraMonitoringGroupBy,
|
||||
useInfraMonitoringOrderBy,
|
||||
useInfraMonitoringVolumeUID,
|
||||
} from '../hooks';
|
||||
import K8sHeader from '../K8sHeader';
|
||||
import LoadingContainer from '../LoadingContainer';
|
||||
import { usePageSize } from '../utils';
|
||||
@@ -48,6 +48,7 @@ import VolumeDetails from './VolumeDetails';
|
||||
|
||||
import '../InfraMonitoringK8s.styles.scss';
|
||||
import './K8sVolumesList.styles.scss';
|
||||
|
||||
function K8sVolumesList({
|
||||
isFiltersVisible,
|
||||
handleFilterVisibilityChange,
|
||||
@@ -61,55 +62,21 @@ function K8sVolumesList({
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const [currentPage, setCurrentPage] = useState(() => {
|
||||
const page = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE);
|
||||
if (page) {
|
||||
return parseInt(page, 10);
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useInfraMonitoringCurrentPage();
|
||||
const [filtersInitialised, setFiltersInitialised] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({
|
||||
...Object.fromEntries(searchParams.entries()),
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE]: currentPage.toString(),
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentPage]);
|
||||
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<string[]>([]);
|
||||
|
||||
const [orderBy, setOrderBy] = useState<{
|
||||
columnName: string;
|
||||
order: 'asc' | 'desc';
|
||||
} | null>(() => getOrderByFromParams(searchParams, true));
|
||||
const [orderBy, setOrderBy] = useInfraMonitoringOrderBy();
|
||||
|
||||
const [selectedVolumeUID, setselectedVolumeUID] = useState<string | null>(
|
||||
() => {
|
||||
const volumeUID = searchParams.get(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VOLUME_UID,
|
||||
);
|
||||
if (volumeUID) {
|
||||
return volumeUID;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
);
|
||||
const [
|
||||
selectedVolumeUID,
|
||||
setselectedVolumeUID,
|
||||
] = useInfraMonitoringVolumeUID();
|
||||
|
||||
const { pageSize, setPageSize } = usePageSize(K8sCategory.VOLUMES);
|
||||
|
||||
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 [groupBy, setGroupBy] = useInfraMonitoringGroupBy();
|
||||
|
||||
const [
|
||||
selectedRowData,
|
||||
@@ -136,7 +103,7 @@ function K8sVolumesList({
|
||||
if (quickFiltersLastUpdated !== -1) {
|
||||
setCurrentPage(1);
|
||||
}
|
||||
}, [quickFiltersLastUpdated]);
|
||||
}, [quickFiltersLastUpdated, setCurrentPage]);
|
||||
|
||||
const { featureFlags } = useAppContext();
|
||||
const dotMetricsEnabled =
|
||||
@@ -240,7 +207,7 @@ function K8sVolumesList({
|
||||
filters: queryFilters,
|
||||
start: Math.floor(minTime / 1000000),
|
||||
end: Math.floor(maxTime / 1000000),
|
||||
orderBy,
|
||||
orderBy: orderBy || baseQuery.orderBy,
|
||||
};
|
||||
if (groupBy.length > 0) {
|
||||
queryPayload.groupBy = groupBy;
|
||||
@@ -313,26 +280,15 @@ function K8sVolumesList({
|
||||
}
|
||||
|
||||
if ('field' in sorter && sorter.order) {
|
||||
const currentOrderBy = {
|
||||
setOrderBy({
|
||||
columnName: sorter.field as string,
|
||||
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],
|
||||
[setCurrentPage, setOrderBy],
|
||||
);
|
||||
|
||||
const { handleChangeQueryData } = useQueryOperations({
|
||||
@@ -393,10 +349,6 @@ 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);
|
||||
}
|
||||
@@ -425,11 +377,6 @@ 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 => (
|
||||
@@ -520,13 +467,6 @@ 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(
|
||||
@@ -547,10 +487,6 @@ 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, {
|
||||
@@ -559,7 +495,7 @@ function K8sVolumesList({
|
||||
category: InfraMonitoringEvents.Volumes,
|
||||
});
|
||||
},
|
||||
[groupByFiltersData?.payload?.attributeKeys, searchParams, setSearchParams],
|
||||
[groupByFiltersData?.payload?.attributeKeys, setCurrentPage, setGroupBy],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag, Tooltip } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import {
|
||||
K8sVolumesData,
|
||||
K8sVolumesListPayload,
|
||||
@@ -75,7 +74,7 @@ export const getK8sVolumesListQuery = (): K8sVolumesListPayload => ({
|
||||
items: [],
|
||||
op: 'and',
|
||||
},
|
||||
orderBy: { columnName: 'cpu', order: 'desc' },
|
||||
orderBy: { columnName: 'usage', order: 'desc' },
|
||||
});
|
||||
|
||||
const columnsConfig = [
|
||||
|
||||
@@ -8,10 +8,13 @@ import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import ClusterDetails from 'container/InfraMonitoringK8s/Clusters/ClusterDetails/ClusterDetails';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import store from 'store';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: {} });
|
||||
|
||||
describe('ClusterDetails', () => {
|
||||
const mockCluster = {
|
||||
meta: {
|
||||
@@ -22,17 +25,19 @@ describe('ClusterDetails', () => {
|
||||
|
||||
it('should render modal with relevant metadata', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const clusterNameElements = screen.getAllByText('test-cluster');
|
||||
@@ -42,17 +47,19 @@ describe('ClusterDetails', () => {
|
||||
|
||||
it('should render modal with 4 tabs', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByText('Metrics');
|
||||
@@ -70,17 +77,19 @@ describe('ClusterDetails', () => {
|
||||
|
||||
it('default tab should be metrics', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
||||
@@ -89,17 +98,19 @@ describe('ClusterDetails', () => {
|
||||
|
||||
it('should switch to events tab when events tab is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
||||
@@ -110,17 +121,19 @@ describe('ClusterDetails', () => {
|
||||
|
||||
it('should close modal when close button is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<ClusterDetails
|
||||
cluster={mockCluster}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||
|
||||
@@ -8,10 +8,13 @@ import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import DaemonSetDetails from 'container/InfraMonitoringK8s/DaemonSets/DaemonSetDetails/DaemonSetDetails';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import store from 'store';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: {} });
|
||||
|
||||
describe('DaemonSetDetails', () => {
|
||||
const mockDaemonSet = {
|
||||
meta: {
|
||||
@@ -24,17 +27,19 @@ describe('DaemonSetDetails', () => {
|
||||
|
||||
it('should render modal with relevant metadata', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const daemonSetNameElements = screen.getAllByText('test-daemon-set');
|
||||
@@ -52,17 +57,19 @@ describe('DaemonSetDetails', () => {
|
||||
|
||||
it('should render modal with 4 tabs', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByText('Metrics');
|
||||
@@ -80,17 +87,19 @@ describe('DaemonSetDetails', () => {
|
||||
|
||||
it('default tab should be metrics', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
||||
@@ -99,17 +108,19 @@ describe('DaemonSetDetails', () => {
|
||||
|
||||
it('should switch to events tab when events tab is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
||||
@@ -120,17 +131,19 @@ describe('DaemonSetDetails', () => {
|
||||
|
||||
it('should close modal when close button is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DaemonSetDetails
|
||||
daemonSet={mockDaemonSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||
|
||||
@@ -8,10 +8,13 @@ import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import DeploymentDetails from 'container/InfraMonitoringK8s/Deployments/DeploymentDetails/DeploymentDetails';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import store from 'store';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: {} });
|
||||
|
||||
describe('DeploymentDetails', () => {
|
||||
const mockDeployment = {
|
||||
meta: {
|
||||
@@ -24,17 +27,19 @@ describe('DeploymentDetails', () => {
|
||||
|
||||
it('should render modal with relevant metadata', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const deploymentNameElements = screen.getAllByText('test-deployment');
|
||||
@@ -52,17 +57,19 @@ describe('DeploymentDetails', () => {
|
||||
|
||||
it('should render modal with 4 tabs', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByText('Metrics');
|
||||
@@ -80,17 +87,19 @@ describe('DeploymentDetails', () => {
|
||||
|
||||
it('default tab should be metrics', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
||||
@@ -99,17 +108,19 @@ describe('DeploymentDetails', () => {
|
||||
|
||||
it('should switch to events tab when events tab is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
||||
@@ -120,17 +131,19 @@ describe('DeploymentDetails', () => {
|
||||
|
||||
it('should close modal when close button is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<DeploymentDetails
|
||||
deployment={mockDeployment}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||
|
||||
@@ -2,8 +2,18 @@ import setupCommonMocks from '../../commonMocks';
|
||||
|
||||
setupCommonMocks();
|
||||
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import JobDetails from 'container/InfraMonitoringK8s/Jobs/JobDetails/JobDetails';
|
||||
import { fireEvent, render, screen } from 'tests/test-utils';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import store from 'store';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: {} });
|
||||
|
||||
describe('JobDetails', () => {
|
||||
const mockJob = {
|
||||
@@ -16,7 +26,15 @@ describe('JobDetails', () => {
|
||||
|
||||
it('should render modal with relevant metadata', () => {
|
||||
render(
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const jobNameElements = screen.getAllByText('test-job');
|
||||
@@ -30,7 +48,15 @@ describe('JobDetails', () => {
|
||||
|
||||
it('should render modal with 4 tabs', () => {
|
||||
render(
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByText('Metrics');
|
||||
@@ -48,7 +74,15 @@ describe('JobDetails', () => {
|
||||
|
||||
it('default tab should be metrics', () => {
|
||||
render(
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
||||
@@ -57,7 +91,15 @@ describe('JobDetails', () => {
|
||||
|
||||
it('should switch to events tab when events tab is clicked', () => {
|
||||
render(
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
||||
@@ -68,7 +110,15 @@ describe('JobDetails', () => {
|
||||
|
||||
it('should close modal when close button is clicked', () => {
|
||||
render(
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<JobDetails job={mockJob} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
import setupCommonMocks from './commonMocks';
|
||||
|
||||
setupCommonMocks();
|
||||
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import K8sHeader from 'container/InfraMonitoringK8s/K8sHeader';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import store from 'store';
|
||||
|
||||
import { INFRA_MONITORING_K8S_PARAMS_KEYS, K8sCategory } from '../constants';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
describe('K8sHeader URL Parameter Parsing', () => {
|
||||
const defaultProps = {
|
||||
selectedGroupBy: [],
|
||||
groupByOptions: [],
|
||||
isLoadingGroupByFilters: false,
|
||||
handleFiltersChange: jest.fn(),
|
||||
handleGroupByChange: jest.fn(),
|
||||
defaultAddedColumns: [],
|
||||
handleFilterVisibilityChange: jest.fn(),
|
||||
isFiltersVisible: true,
|
||||
entity: K8sCategory.PODS,
|
||||
showAutoRefresh: true,
|
||||
};
|
||||
|
||||
const renderComponent = (
|
||||
searchParams?: string | Record<string, string>,
|
||||
): ReturnType<typeof render> => {
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: searchParams ?? {} });
|
||||
return render(
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<K8sHeader {...defaultProps} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render without crashing when no URL params', () => {
|
||||
expect(() => renderComponent()).not.toThrow();
|
||||
expect(screen.getByText('Group by')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render without crashing with valid filters in URL', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'k8s_namespace_name' },
|
||||
op: '=',
|
||||
value: 'kube-system',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
renderComponent({
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS]: JSON.stringify(filters),
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should render without crashing with malformed filters JSON', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
expect(() =>
|
||||
renderComponent({
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS]: 'invalid-json',
|
||||
}),
|
||||
).not.toThrow();
|
||||
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle filters with K8s container image values', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'k8s_container_image' },
|
||||
op: '=',
|
||||
value: 'registry.k8s.io/coredns/coredns:v1.10.1',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
renderComponent({
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS]: JSON.stringify(filters),
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle filters with percent signs in values', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'k8s_label' },
|
||||
op: '=',
|
||||
value: 'cpu-usage-50%',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
renderComponent({
|
||||
[INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS]: JSON.stringify(filters),
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -8,10 +8,13 @@ import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import NamespaceDetails from 'container/InfraMonitoringK8s/Namespaces/NamespaceDetails/NamespaceDetails';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import store from 'store';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: {} });
|
||||
|
||||
describe('NamespaceDetails', () => {
|
||||
const mockNamespace = {
|
||||
namespaceName: 'test-namespace',
|
||||
@@ -23,17 +26,19 @@ describe('NamespaceDetails', () => {
|
||||
|
||||
it('should render modal with relevant metadata', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const namespaceNameElements = screen.getAllByText('test-namespace');
|
||||
@@ -47,17 +52,19 @@ describe('NamespaceDetails', () => {
|
||||
|
||||
it('should render modal with 4 tabs', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByText('Metrics');
|
||||
@@ -75,17 +82,19 @@ describe('NamespaceDetails', () => {
|
||||
|
||||
it('default tab should be metrics', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
||||
@@ -94,17 +103,19 @@ describe('NamespaceDetails', () => {
|
||||
|
||||
it('should switch to events tab when events tab is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
||||
@@ -115,17 +126,19 @@ describe('NamespaceDetails', () => {
|
||||
|
||||
it('should close modal when close button is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NamespaceDetails
|
||||
namespace={mockNamespace}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||
|
||||
@@ -8,10 +8,13 @@ import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import NodeDetails from 'container/InfraMonitoringK8s/Nodes/NodeDetails/NodeDetails';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import store from 'store';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: {} });
|
||||
|
||||
describe('NodeDetails', () => {
|
||||
const mockNode = {
|
||||
meta: {
|
||||
@@ -23,13 +26,19 @@ describe('NodeDetails', () => {
|
||||
|
||||
it('should render modal with relevant metadata', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails node={mockNode} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails
|
||||
node={mockNode}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const nodeNameElements = screen.getAllByText('test-node');
|
||||
@@ -43,13 +52,19 @@ describe('NodeDetails', () => {
|
||||
|
||||
it('should render modal with 4 tabs', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails node={mockNode} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails
|
||||
node={mockNode}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByText('Metrics');
|
||||
@@ -67,13 +82,19 @@ describe('NodeDetails', () => {
|
||||
|
||||
it('default tab should be metrics', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails node={mockNode} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails
|
||||
node={mockNode}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
||||
@@ -82,13 +103,19 @@ describe('NodeDetails', () => {
|
||||
|
||||
it('should switch to events tab when events tab is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails node={mockNode} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails
|
||||
node={mockNode}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
||||
@@ -99,13 +126,19 @@ describe('NodeDetails', () => {
|
||||
|
||||
it('should close modal when close button is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails node={mockNode} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<NodeDetails
|
||||
node={mockNode}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||
|
||||
155
frontend/src/container/InfraMonitoringK8s/__tests__/Pods/K8sPodsList.test.tsx
generated
Normal file
155
frontend/src/container/InfraMonitoringK8s/__tests__/Pods/K8sPodsList.test.tsx
generated
Normal file
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Tests for URL parameter parsing in K8s Infra Monitoring components.
|
||||
*
|
||||
* These tests verify the fix for the double URL decoding bug where
|
||||
* components were calling decodeURIComponent() on values already
|
||||
* decoded by URLSearchParams.get(), causing crashes on K8s parameters
|
||||
* with special characters.
|
||||
*/
|
||||
|
||||
import { getFiltersFromParams } from '../../commonUtils';
|
||||
|
||||
describe('K8sPodsList URL Parameter Parsing', () => {
|
||||
describe('getFiltersFromParams', () => {
|
||||
it('should return null when no filters in params', () => {
|
||||
const searchParams = new URLSearchParams();
|
||||
const result = getFiltersFromParams(searchParams, 'filters');
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it('should parse filters from URL params', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'k8s_namespace_name' },
|
||||
op: '=',
|
||||
value: 'default',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('filters', JSON.stringify(filters));
|
||||
|
||||
const result = getFiltersFromParams(searchParams, 'filters');
|
||||
expect(result).toEqual(filters);
|
||||
});
|
||||
|
||||
it('should handle URL-encoded filters (auto-decoded by URLSearchParams)', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'k8s_pod_name' },
|
||||
op: 'contains',
|
||||
value: 'api-server',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
const encodedValue = encodeURIComponent(JSON.stringify(filters));
|
||||
const searchParams = new URLSearchParams(`filters=${encodedValue}`);
|
||||
|
||||
const result = getFiltersFromParams(searchParams, 'filters');
|
||||
expect(result).toEqual(filters);
|
||||
});
|
||||
|
||||
it('should return null on malformed JSON instead of crashing', () => {
|
||||
const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const searchParams = new URLSearchParams();
|
||||
searchParams.set('filters', '{invalid-json}');
|
||||
|
||||
const result = getFiltersFromParams(searchParams, 'filters');
|
||||
expect(result).toBeNull();
|
||||
consoleSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('should handle filters with K8s container image names', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'k8s_container_name' },
|
||||
op: '=',
|
||||
value: 'registry.k8s.io/coredns/coredns:v1.10.1',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
const encodedValue = encodeURIComponent(JSON.stringify(filters));
|
||||
const searchParams = new URLSearchParams(`filters=${encodedValue}`);
|
||||
|
||||
const result = getFiltersFromParams(searchParams, 'filters');
|
||||
expect(result).toEqual(filters);
|
||||
});
|
||||
});
|
||||
|
||||
describe('regression: double decoding issue', () => {
|
||||
it('should not crash when URL params are already decoded by URLSearchParams', () => {
|
||||
// The key bug: URLSearchParams.get() auto-decodes, so encoding once in URL
|
||||
// means .get() returns decoded value. Old code called decodeURIComponent()
|
||||
// again which could crash on certain characters.
|
||||
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'k8s_namespace_name' },
|
||||
op: '=',
|
||||
value: 'kube-system',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const encodedValue = encodeURIComponent(JSON.stringify(filters));
|
||||
const searchParams = new URLSearchParams(`filters=${encodedValue}`);
|
||||
|
||||
// This should work without crashing
|
||||
const result = getFiltersFromParams(searchParams, 'filters');
|
||||
expect(result).toEqual(filters);
|
||||
});
|
||||
|
||||
it('should handle values with percent signs in labels', () => {
|
||||
// K8s labels might contain literal "%" characters like "cpu-usage-50%"
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'k8s_label' },
|
||||
op: '=',
|
||||
value: 'cpu-50%',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const encodedValue = encodeURIComponent(JSON.stringify(filters));
|
||||
const searchParams = new URLSearchParams(`filters=${encodedValue}`);
|
||||
|
||||
const result = getFiltersFromParams(searchParams, 'filters');
|
||||
expect(result).toEqual(filters);
|
||||
});
|
||||
|
||||
it('should handle complex K8s deployment names with special chars', () => {
|
||||
const filters = {
|
||||
items: [
|
||||
{
|
||||
id: '1',
|
||||
key: { key: 'k8s_deployment_name' },
|
||||
op: '=',
|
||||
value: 'nginx-ingress-controller',
|
||||
},
|
||||
],
|
||||
op: 'AND',
|
||||
};
|
||||
|
||||
const encodedValue = encodeURIComponent(JSON.stringify(filters));
|
||||
const searchParams = new URLSearchParams(`filters=${encodedValue}`);
|
||||
|
||||
const result = getFiltersFromParams(searchParams, 'filters');
|
||||
expect(result).toEqual(filters);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8,10 +8,13 @@ import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import PodDetails from 'container/InfraMonitoringK8s/Pods/PodDetails/PodDetails';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import store from 'store';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: {} });
|
||||
|
||||
describe('PodDetails', () => {
|
||||
const mockPod = {
|
||||
podName: 'test-pod',
|
||||
@@ -25,13 +28,15 @@ describe('PodDetails', () => {
|
||||
|
||||
it('should render modal with relevant metadata', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const clusterNameElements = screen.getAllByText('test-cluster');
|
||||
@@ -49,13 +54,15 @@ describe('PodDetails', () => {
|
||||
|
||||
it('should render modal with 4 tabs', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByText('Metrics');
|
||||
@@ -73,13 +80,15 @@ describe('PodDetails', () => {
|
||||
|
||||
it('default tab should be metrics', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
||||
@@ -88,13 +97,15 @@ describe('PodDetails', () => {
|
||||
|
||||
it('should switch to events tab when events tab is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
||||
@@ -105,13 +116,15 @@ describe('PodDetails', () => {
|
||||
|
||||
it('should close modal when close button is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<PodDetails pod={mockPod} isModalTimeSelection onClose={mockOnClose} />
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||
|
||||
@@ -8,10 +8,13 @@ import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import StatefulSetDetails from 'container/InfraMonitoringK8s/StatefulSets/StatefulSetDetails/StatefulSetDetails';
|
||||
import { withNuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import store from 'store';
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
const Wrapper = withNuqsTestingAdapter({ searchParams: {} });
|
||||
|
||||
describe('StatefulSetDetails', () => {
|
||||
const mockStatefulSet = {
|
||||
meta: {
|
||||
@@ -23,17 +26,19 @@ describe('StatefulSetDetails', () => {
|
||||
|
||||
it('should render modal with relevant metadata', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const statefulSetNameElements = screen.getAllByText('test-stateful-set');
|
||||
@@ -47,17 +52,19 @@ describe('StatefulSetDetails', () => {
|
||||
|
||||
it('should render modal with 4 tabs', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByText('Metrics');
|
||||
@@ -75,17 +82,19 @@ describe('StatefulSetDetails', () => {
|
||||
|
||||
it('default tab should be metrics', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const metricsTab = screen.getByRole('radio', { name: 'Metrics' });
|
||||
@@ -94,17 +103,19 @@ describe('StatefulSetDetails', () => {
|
||||
|
||||
it('should switch to events tab when events tab is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const eventsTab = screen.getByRole('radio', { name: 'Events' });
|
||||
@@ -115,17 +126,19 @@ describe('StatefulSetDetails', () => {
|
||||
|
||||
it('should close modal when close button is clicked', () => {
|
||||
render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>,
|
||||
<Wrapper>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Provider store={store}>
|
||||
<MemoryRouter>
|
||||
<StatefulSetDetails
|
||||
statefulSet={mockStatefulSet}
|
||||
isModalTimeSelection
|
||||
onClose={mockOnClose}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
</QueryClientProvider>
|
||||
</Wrapper>,
|
||||
);
|
||||
|
||||
const closeButton = screen.getByRole('button', { name: 'Close' });
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createElement } from 'react';
|
||||
import * as appContextHooks from 'providers/App/App';
|
||||
import * as timezoneHooks from 'providers/Timezone';
|
||||
import { LicenseEvent } from 'types/api/licensesV3/getActive';
|
||||
@@ -45,14 +46,6 @@ const setupCommonMocks = (): void => {
|
||||
|
||||
jest.mock('react-router-dom-v5-compat', () => ({
|
||||
...jest.requireActual('react-router-dom-v5-compat'),
|
||||
useSearchParams: jest.fn().mockReturnValue([
|
||||
{
|
||||
get: jest.fn(),
|
||||
entries: jest.fn(() => []),
|
||||
set: jest.fn(),
|
||||
},
|
||||
jest.fn(),
|
||||
]),
|
||||
useNavigationType: (): any => 'PUSH',
|
||||
}));
|
||||
|
||||
@@ -103,6 +96,15 @@ const setupCommonMocks = (): void => {
|
||||
safeNavigate: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// TODO: Remove this when https://github.com/SigNoz/engineering-pod/issues/4253
|
||||
jest.mock('container/TopNav/DateTimeSelectionV2', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
default: (): React.ReactElement =>
|
||||
createElement('div', { 'data-testid': 'datetime-selection' }),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
export default setupCommonMocks;
|
||||
|
||||
@@ -12,11 +12,7 @@ import {
|
||||
TagFilterItem,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
|
||||
import {
|
||||
getInvalidValueTooltipText,
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS,
|
||||
K8sCategory,
|
||||
} from './constants';
|
||||
import { getInvalidValueTooltipText, K8sCategory } from './constants';
|
||||
|
||||
/**
|
||||
* Converts size in bytes to a human-readable string with appropriate units
|
||||
@@ -254,27 +250,6 @@ 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,
|
||||
@@ -282,10 +257,9 @@ export const getFiltersFromParams = (
|
||||
const filtersFromParams = searchParams.get(queryKey);
|
||||
if (filtersFromParams) {
|
||||
try {
|
||||
const decoded = decodeURIComponent(filtersFromParams);
|
||||
const parsed = JSON.parse(decoded);
|
||||
const parsed = JSON.parse(filtersFromParams);
|
||||
return parsed as IBuilderQuery['filters'];
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
164
frontend/src/container/InfraMonitoringK8s/hooks.ts
Normal file
164
frontend/src/container/InfraMonitoringK8s/hooks.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { VIEWS } from 'components/HostMetricsDetail/constants';
|
||||
import {
|
||||
Options,
|
||||
parseAsInteger,
|
||||
parseAsJson,
|
||||
parseAsString,
|
||||
useQueryState,
|
||||
} from 'nuqs';
|
||||
import {
|
||||
IBuilderQuery,
|
||||
TagFilter,
|
||||
} from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { parseAsJsonNoValidate } from 'utils/nuqsParsers';
|
||||
|
||||
import { INFRA_MONITORING_K8S_PARAMS_KEYS, K8sCategories } from './constants';
|
||||
import { orderBySchema } from './schemas';
|
||||
|
||||
const defaultFilters: IBuilderQuery['filters'] = { items: [], op: 'and' };
|
||||
const defaultNuqsOptions: Options = {
|
||||
history: 'push',
|
||||
};
|
||||
|
||||
export const useInfraMonitoringCurrentPage = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.CURRENT_PAGE,
|
||||
parseAsInteger.withDefault(1).withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringOrderBy = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY,
|
||||
parseAsJson(orderBySchema).withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringOrderByHosts = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.ORDER_BY,
|
||||
parseAsJson(orderBySchema)
|
||||
.withDefault({
|
||||
columnName: 'cpu',
|
||||
order: 'desc',
|
||||
})
|
||||
.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringGroupBy = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.GROUP_BY,
|
||||
parseAsJsonNoValidate<IBuilderQuery['groupBy']>()
|
||||
.withDefault([])
|
||||
.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringView = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW,
|
||||
parseAsString.withDefault(VIEWS.METRICS).withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringLogFilters = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS,
|
||||
parseAsJsonNoValidate<IBuilderQuery['filters']>().withOptions(
|
||||
defaultNuqsOptions,
|
||||
),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringTracesFilters = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS,
|
||||
parseAsJsonNoValidate<IBuilderQuery['filters']>().withOptions(
|
||||
defaultNuqsOptions,
|
||||
),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringEventsFilters = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.EVENTS_FILTERS,
|
||||
parseAsJsonNoValidate<IBuilderQuery['filters']>().withOptions(
|
||||
defaultNuqsOptions,
|
||||
),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringCategory = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.CATEGORY,
|
||||
parseAsString.withDefault(K8sCategories.PODS).withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringFilters = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS,
|
||||
parseAsString.withDefault('').withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringFiltersK8s = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS,
|
||||
parseAsJsonNoValidate<TagFilter>().withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringFiltersHosts = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.FILTERS,
|
||||
parseAsJsonNoValidate<IBuilderQuery['filters']>()
|
||||
.withDefault(defaultFilters)
|
||||
.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringClusterName = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.CLUSTER_NAME,
|
||||
parseAsString.withDefault('').withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringDaemonSetUID = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.DAEMONSET_UID,
|
||||
parseAsString.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringDeploymentUID = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.DEPLOYMENT_UID,
|
||||
parseAsString.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringJobUID = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.JOB_UID,
|
||||
parseAsString.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringNamespaceUID = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.NAMESPACE_UID,
|
||||
parseAsString.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringNodeUID = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.NODE_UID,
|
||||
parseAsString.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringPodUID = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.POD_UID,
|
||||
parseAsString.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringStatefulSetUID = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.STATEFULSET_UID,
|
||||
parseAsString.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
|
||||
export const useInfraMonitoringVolumeUID = () =>
|
||||
useQueryState(
|
||||
INFRA_MONITORING_K8S_PARAMS_KEYS.VOLUME_UID,
|
||||
parseAsString.withOptions(defaultNuqsOptions),
|
||||
);
|
||||
10
frontend/src/container/InfraMonitoringK8s/schemas.ts
Normal file
10
frontend/src/container/InfraMonitoringK8s/schemas.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const orderBySchema = z
|
||||
.object({
|
||||
columnName: z.string(),
|
||||
order: z.enum(['asc', 'desc']),
|
||||
})
|
||||
.nullable();
|
||||
|
||||
export type OrderBySchemaType = z.infer<typeof orderBySchema>;
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Tag, Tooltip } from 'antd';
|
||||
import { TableColumnType as ColumnType } from 'antd';
|
||||
import { TableColumnType as ColumnType, Tag, Tooltip } from 'antd';
|
||||
import get from 'api/browser/localstorage/get';
|
||||
import set from 'api/browser/localstorage/set';
|
||||
import {
|
||||
@@ -17,6 +16,7 @@ import {
|
||||
ValidateColumnValueWrapper,
|
||||
} from './commonUtils';
|
||||
import { DEFAULT_PAGE_SIZE, K8sCategory } from './constants';
|
||||
import { OrderBySchemaType } from './schemas';
|
||||
|
||||
import './InfraMonitoringK8s.styles.scss';
|
||||
|
||||
@@ -148,7 +148,7 @@ export const dummyColumnConfig = {
|
||||
className: 'column column-dummy',
|
||||
};
|
||||
|
||||
const columnsConfig = [
|
||||
const columnsConfig: ColumnType<any>[] = [
|
||||
{
|
||||
title: <div className="column-header pod-name-header">Pod Name</div>,
|
||||
dataIndex: 'podName',
|
||||
@@ -231,7 +231,7 @@ const columnsConfig = [
|
||||
// },
|
||||
];
|
||||
|
||||
export const namespaceColumnConfig = {
|
||||
export const namespaceColumnConfig: ColumnType<any> = {
|
||||
title: <div className="column-header">Namespace</div>,
|
||||
dataIndex: 'namespace',
|
||||
key: 'namespace',
|
||||
@@ -242,7 +242,7 @@ export const namespaceColumnConfig = {
|
||||
className: 'column column-namespace',
|
||||
};
|
||||
|
||||
export const nodeColumnConfig = {
|
||||
export const nodeColumnConfig: ColumnType<any> = {
|
||||
title: <div className="column-header">Node</div>,
|
||||
dataIndex: 'node',
|
||||
key: 'node',
|
||||
@@ -253,7 +253,7 @@ export const nodeColumnConfig = {
|
||||
className: 'column column-node',
|
||||
};
|
||||
|
||||
export const clusterColumnConfig = {
|
||||
export const clusterColumnConfig: ColumnType<any> = {
|
||||
title: <div className="column-header">Cluster</div>,
|
||||
dataIndex: 'cluster',
|
||||
key: 'cluster',
|
||||
@@ -264,7 +264,7 @@ export const clusterColumnConfig = {
|
||||
className: 'column column-cluster',
|
||||
};
|
||||
|
||||
export const columnConfigMap = {
|
||||
export const columnConfigMap: Record<string, ColumnType<any>> = {
|
||||
namespace: namespaceColumnConfig,
|
||||
node: nodeColumnConfig,
|
||||
cluster: clusterColumnConfig,
|
||||
@@ -273,8 +273,9 @@ export const columnConfigMap = {
|
||||
export const getK8sPodsListColumns = (
|
||||
addedColumns: IEntityColumn[],
|
||||
groupBy: IBuilderQuery['groupBy'],
|
||||
defaultOrderBy: OrderBySchemaType,
|
||||
): ColumnType<K8sPodsRowData>[] => {
|
||||
const updatedColumnsConfig = [...columnsConfig];
|
||||
const updatedColumnsConfig: ColumnType<K8sPodsRowData>[] = [...columnsConfig];
|
||||
|
||||
for (const column of addedColumns) {
|
||||
const config = columnConfigMap[column.id as keyof typeof columnConfigMap];
|
||||
@@ -293,7 +294,14 @@ export const getK8sPodsListColumns = (
|
||||
return filteredColumns as ColumnType<K8sPodsRowData>[];
|
||||
}
|
||||
|
||||
return updatedColumnsConfig as ColumnType<K8sPodsRowData>[];
|
||||
for (const column of updatedColumnsConfig) {
|
||||
if (column.sorter && column.key === defaultOrderBy?.columnName) {
|
||||
column.defaultSortOrder =
|
||||
defaultOrderBy?.order === 'asc' ? 'ascend' : 'descend';
|
||||
}
|
||||
}
|
||||
|
||||
return updatedColumnsConfig;
|
||||
};
|
||||
|
||||
const dotToUnder: Record<string, keyof K8sPodsData['meta']> = {
|
||||
|
||||
16
frontend/src/utils/nuqsParsers.ts
Normal file
16
frontend/src/utils/nuqsParsers.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createParser, SingleParserBuilder } from 'nuqs';
|
||||
|
||||
export function parseAsJsonNoValidate<T = unknown>(): SingleParserBuilder<T> {
|
||||
return createParser<T>({
|
||||
parse: (query: string): T | null => {
|
||||
try {
|
||||
return JSON.parse(query) as T;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
serialize: (value: T): string => JSON.stringify(value),
|
||||
eq: (a: T, b: T): boolean =>
|
||||
a === b || JSON.stringify(a) === JSON.stringify(b),
|
||||
});
|
||||
}
|
||||
@@ -20238,6 +20238,11 @@ yocto-queue@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.2.2.tgz#3e09c95d3f1aa89a58c114c99223edf639152c00"
|
||||
integrity sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==
|
||||
|
||||
zod@4.3.6:
|
||||
version "4.3.6"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.6.tgz#89c56e0aa7d2b05107d894412227087885ab112a"
|
||||
integrity sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==
|
||||
|
||||
zustand@5.0.11:
|
||||
version "5.0.11"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-5.0.11.tgz#99f912e590de1ca9ce6c6d1cab6cdb1f034ab494"
|
||||
|
||||
2
go.mod
2
go.mod
@@ -11,7 +11,6 @@ require (
|
||||
github.com/SigNoz/signoz-otel-collector v0.144.2
|
||||
github.com/antlr4-go/antlr/v4 v4.13.1
|
||||
github.com/antonmedv/expr v1.15.3
|
||||
github.com/bytedance/sonic v1.14.1
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/coreos/go-oidc/v3 v3.17.0
|
||||
github.com/dgraph-io/ristretto/v2 v2.3.0
|
||||
@@ -106,6 +105,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect
|
||||
github.com/aws/smithy-go v1.24.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/http/render"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
@@ -41,9 +40,7 @@ func (middleware *AuthZ) ViewAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
commentCtx := ctxtypes.CommentFromContext(ctx)
|
||||
authtype, ok := commentCtx.Map()["auth_type"]
|
||||
if ok && (authtype == authtypes.IdentNProviderAPIkey.StringValue()) {
|
||||
if claims.IdentNProvider == authtypes.IdentNProviderAPIkey.StringValue() {
|
||||
if err := claims.IsViewer(); err != nil {
|
||||
middleware.logger.WarnContext(ctx, authzDeniedMessage, "claims", claims)
|
||||
render.Error(rw, err)
|
||||
@@ -93,9 +90,7 @@ func (middleware *AuthZ) EditAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
commentCtx := ctxtypes.CommentFromContext(ctx)
|
||||
authtype, ok := commentCtx.Map()["auth_type"]
|
||||
if ok && (authtype == authtypes.IdentNProviderAPIkey.StringValue()) {
|
||||
if claims.IdentNProvider == authtypes.IdentNProviderAPIkey.StringValue() {
|
||||
if err := claims.IsEditor(); err != nil {
|
||||
middleware.logger.WarnContext(ctx, authzDeniedMessage, "claims", claims)
|
||||
render.Error(rw, err)
|
||||
@@ -144,9 +139,7 @@ func (middleware *AuthZ) AdminAccess(next http.HandlerFunc) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
commentCtx := ctxtypes.CommentFromContext(ctx)
|
||||
authtype, ok := commentCtx.Map()["auth_type"]
|
||||
if ok && (authtype == authtypes.IdentNProviderAPIkey.StringValue()) {
|
||||
if claims.IdentNProvider == authtypes.IdentNProviderAPIkey.StringValue() {
|
||||
if err := claims.IsAdmin(); err != nil {
|
||||
middleware.logger.WarnContext(ctx, authzDeniedMessage, "claims", claims)
|
||||
render.Error(rw, err)
|
||||
|
||||
@@ -101,13 +101,8 @@ func (provider *provider) GetIdentity(req *http.Request) (*authtypes.Identity, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
identity := authtypes.Identity{
|
||||
UserID: user.ID,
|
||||
Role: apiKey.Role,
|
||||
Email: user.Email,
|
||||
OrgID: user.OrgID,
|
||||
}
|
||||
return &identity, nil
|
||||
identity := authtypes.NewIdentity(user.ID, user.OrgID, user.Email, apiKey.Role, provider.Name())
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
func (provider *provider) Post(ctx context.Context, _ *http.Request, _ authtypes.Claims) {
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
package implcloudintegration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
store sqlstore.SQLStore
|
||||
}
|
||||
|
||||
func NewStore(sqlStore sqlstore.SQLStore) cloudintegrationtypes.Store {
|
||||
return &store{store: sqlStore}
|
||||
}
|
||||
|
||||
func (s *store) GetAccountByID(ctx context.Context, orgID, id valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.StorableCloudIntegration, error) {
|
||||
account := new(cloudintegrationtypes.StorableCloudIntegration)
|
||||
err := s.store.BunDBCtx(ctx).NewSelect().Model(account).
|
||||
Where("id = ?", id).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("provider = ?", provider).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.store.WrapNotFoundErrf(err, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration account with id %s not found", id)
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *store) ListConnectedAccounts(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) ([]*cloudintegrationtypes.StorableCloudIntegration, error) {
|
||||
var accounts []*cloudintegrationtypes.StorableCloudIntegration
|
||||
err := s.store.BunDBCtx(ctx).NewSelect().Model(&accounts).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("provider = ?", provider).
|
||||
Where("removed_at IS NULL").
|
||||
Where("account_id IS NOT NULL").
|
||||
Where("last_agent_report IS NOT NULL").
|
||||
Order("created_at ASC").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (s *store) CreateAccount(ctx context.Context, account *cloudintegrationtypes.StorableCloudIntegration) (*cloudintegrationtypes.StorableCloudIntegration, error) {
|
||||
_, err := s.store.BunDBCtx(ctx).NewInsert().Model(account).Exec(ctx)
|
||||
if err != nil {
|
||||
return nil, s.store.WrapAlreadyExistsErrf(err, cloudintegrationtypes.ErrCodeCloudIntegrationAlreadyExists, "cloud integration account with id %s already exists", account.ID)
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *store) UpdateAccount(ctx context.Context, account *cloudintegrationtypes.StorableCloudIntegration) error {
|
||||
_, err := s.store.BunDBCtx(ctx).
|
||||
NewUpdate().
|
||||
Model(account).
|
||||
WherePK().
|
||||
Where("org_id = ?", account.OrgID).
|
||||
Where("provider = ?", account.Provider).
|
||||
Exec(ctx)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *store) RemoveAccount(ctx context.Context, orgID, id valuer.UUID, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
_, err := s.store.BunDBCtx(ctx).NewUpdate().Model(new(cloudintegrationtypes.StorableCloudIntegration)).
|
||||
Set("removed_at = ?", time.Now()).
|
||||
Where("id = ?", id).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("provider = ?", provider).
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *store) GetConnectedAccount(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, providerAccountID string) (*cloudintegrationtypes.StorableCloudIntegration, error) {
|
||||
account := new(cloudintegrationtypes.StorableCloudIntegration)
|
||||
err := s.store.BunDBCtx(ctx).NewSelect().Model(account).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("provider = ?", provider).
|
||||
Where("account_id = ?", providerAccountID).
|
||||
Where("last_agent_report IS NOT NULL").
|
||||
Where("removed_at IS NULL").
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.store.WrapNotFoundErrf(err, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "connected account with provider account id %s not found", providerAccountID)
|
||||
}
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (s *store) GetServiceByServiceID(ctx context.Context, cloudIntegrationID valuer.UUID, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.StorableCloudIntegrationService, error) {
|
||||
service := new(cloudintegrationtypes.StorableCloudIntegrationService)
|
||||
err := s.store.BunDBCtx(ctx).NewSelect().Model(service).
|
||||
Where("cloud_integration_id = ?", cloudIntegrationID).
|
||||
Where("type = ?", serviceID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, s.store.WrapNotFoundErrf(err, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration service with id %s not found", serviceID)
|
||||
}
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *store) ListServices(ctx context.Context, cloudIntegrationID valuer.UUID) ([]*cloudintegrationtypes.StorableCloudIntegrationService, error) {
|
||||
var services []*cloudintegrationtypes.StorableCloudIntegrationService
|
||||
err := s.store.BunDBCtx(ctx).NewSelect().Model(&services).
|
||||
Where("cloud_integration_id = ?", cloudIntegrationID).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return services, nil
|
||||
}
|
||||
|
||||
func (s *store) CreateService(ctx context.Context, service *cloudintegrationtypes.StorableCloudIntegrationService) (*cloudintegrationtypes.StorableCloudIntegrationService, error) {
|
||||
_, err := s.store.BunDBCtx(ctx).NewInsert().Model(service).Exec(ctx)
|
||||
if err != nil {
|
||||
return nil, s.store.WrapAlreadyExistsErrf(err, cloudintegrationtypes.ErrCodeCloudIntegrationServiceAlreadyExists, "cloud integration service with id %s already exists for integration account", service.Type)
|
||||
}
|
||||
|
||||
return service, nil
|
||||
}
|
||||
|
||||
func (s *store) UpdateService(ctx context.Context, service *cloudintegrationtypes.StorableCloudIntegrationService) error {
|
||||
_, err := s.store.BunDBCtx(ctx).NewUpdate().Model(service).
|
||||
WherePK().
|
||||
Where("cloud_integration_id = ?", service.CloudIntegrationID).
|
||||
Where("type = ?", service.Type).
|
||||
Exec(ctx)
|
||||
return err
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func (m *module) ListPromotedAndIndexedPaths(ctx context.Context) ([]promotetype
|
||||
|
||||
// add the paths that are not promoted but have indexes
|
||||
for path, indexes := range aggr {
|
||||
path := strings.TrimPrefix(path, telemetrylogs.BodyJSONColumnPrefix)
|
||||
path := strings.TrimPrefix(path, telemetrylogs.BodyV2ColumnPrefix)
|
||||
path = telemetrytypes.BodyJSONStringSearchPrefix + path
|
||||
response = append(response, promotetypes.PromotePath{
|
||||
Path: path,
|
||||
@@ -163,7 +163,7 @@ func (m *module) PromoteAndIndexPaths(
|
||||
}
|
||||
}
|
||||
if len(it.Indexes) > 0 {
|
||||
parentColumn := telemetrylogs.LogsV2BodyJSONColumn
|
||||
parentColumn := telemetrylogs.LogsV2BodyV2Column
|
||||
// if the path is already promoted or is being promoted, add it to the promoted column
|
||||
if _, promoted := existingPromotedPaths[it.Path]; promoted || it.Promote {
|
||||
parentColumn = telemetrylogs.LogsV2BodyPromotedColumn
|
||||
|
||||
@@ -10,13 +10,11 @@ import (
|
||||
|
||||
"github.com/ClickHouse/clickhouse-go/v2"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrylogs"
|
||||
"github.com/SigNoz/signoz/pkg/telemetrystore"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/instrumentationtypes"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type builderQuery[T any] struct {
|
||||
@@ -262,40 +260,6 @@ func (q *builderQuery[T]) executeWithContext(ctx context.Context, query string,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// merge body_json and promoted into body
|
||||
if q.spec.Signal == telemetrytypes.SignalLogs {
|
||||
switch typedPayload := payload.(type) {
|
||||
case *qbtypes.RawData:
|
||||
for _, rr := range typedPayload.Rows {
|
||||
seeder := func() error {
|
||||
body, ok := rr.Data[telemetrylogs.LogsV2BodyJSONColumn].(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
promoted, ok := rr.Data[telemetrylogs.LogsV2BodyPromotedColumn].(map[string]any)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
seed(promoted, body)
|
||||
str, err := sonic.MarshalString(body)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to marshal body")
|
||||
}
|
||||
rr.Data["body"] = str
|
||||
return nil
|
||||
}
|
||||
err := seeder()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
delete(rr.Data, telemetrylogs.LogsV2BodyJSONColumn)
|
||||
delete(rr.Data, telemetrylogs.LogsV2BodyPromotedColumn)
|
||||
}
|
||||
payload = typedPayload
|
||||
}
|
||||
}
|
||||
|
||||
return &qbtypes.Result{
|
||||
Type: q.kind,
|
||||
Value: payload,
|
||||
@@ -423,18 +387,3 @@ func decodeCursor(cur string) (int64, error) {
|
||||
}
|
||||
return strconv.ParseInt(string(b), 10, 64)
|
||||
}
|
||||
|
||||
func seed(promoted map[string]any, body map[string]any) {
|
||||
for key, fromValue := range promoted {
|
||||
if toValue, ok := body[key]; !ok {
|
||||
body[key] = fromValue
|
||||
} else {
|
||||
if fromValue, ok := fromValue.(map[string]any); ok {
|
||||
if toValue, ok := toValue.(map[string]any); ok {
|
||||
seed(fromValue, toValue)
|
||||
body[key] = toValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/ClickHouse/clickhouse-go/v2/lib/driver"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -394,17 +393,11 @@ func readAsRaw(rows driver.Rows, queryName string) (*qbtypes.RawData, error) {
|
||||
|
||||
// de-reference the typed pointer to any
|
||||
val := reflect.ValueOf(cellPtr).Elem().Interface()
|
||||
|
||||
// Post-process JSON columns: normalize into structured values
|
||||
// Post-process JSON columns: normalize into String value
|
||||
if strings.HasPrefix(strings.ToUpper(colTypes[i].DatabaseTypeName()), "JSON") {
|
||||
switch x := val.(type) {
|
||||
case []byte:
|
||||
if len(x) > 0 {
|
||||
var v any
|
||||
if err := sonic.Unmarshal(x, &v); err == nil {
|
||||
val = v
|
||||
}
|
||||
}
|
||||
val = string(x)
|
||||
default:
|
||||
// already a structured type (map[string]any, []any, etc.)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
opentracing "github.com/opentracing/opentracing-go"
|
||||
plabels "github.com/prometheus/prometheus/model/labels"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
// PromRuleTask is a promql rule executor
|
||||
@@ -371,7 +373,7 @@ func (g *PromRuleTask) Eval(ctx context.Context, ts time.Time) {
|
||||
|
||||
comment := ctxtypes.CommentFromContext(ctx)
|
||||
comment.Set("rule_id", rule.ID())
|
||||
comment.Set("auth_type", "internal")
|
||||
comment.Set("identn_provider", authtypes.IdentNProviderInternal.StringValue())
|
||||
ctx = ctxtypes.NewContextWithComment(ctx, comment)
|
||||
|
||||
_, err := rule.Eval(ctx, ts)
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/query-service/utils/labels"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/ctxtypes"
|
||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
@@ -358,7 +359,7 @@ func (g *RuleTask) Eval(ctx context.Context, ts time.Time) {
|
||||
|
||||
comment := ctxtypes.CommentFromContext(ctx)
|
||||
comment.Set("rule_id", rule.ID())
|
||||
comment.Set("auth_type", "internal")
|
||||
comment.Set("identn_provider", authtypes.IdentNProviderInternal.StringValue())
|
||||
ctx = ctxtypes.NewContextWithComment(ctx, comment)
|
||||
|
||||
_, err := rule.Eval(ctx, ts)
|
||||
|
||||
@@ -219,7 +219,6 @@ func DataTypeCollisionHandledFieldName(key *telemetrytypes.TelemetryFieldKey, va
|
||||
// we don't have a toBoolOrNull in ClickHouse, so we need to convert the bool to a string
|
||||
value = fmt.Sprintf("%t", v)
|
||||
}
|
||||
|
||||
case telemetrytypes.FieldDataTypeInt64,
|
||||
telemetrytypes.FieldDataTypeArrayInt64,
|
||||
telemetrytypes.FieldDataTypeNumber,
|
||||
|
||||
@@ -313,37 +313,30 @@ func (v *filterExpressionVisitor) VisitPrimary(ctx *grammar.PrimaryContext) any
|
||||
return ""
|
||||
}
|
||||
child := ctx.GetChild(0)
|
||||
var searchText string
|
||||
if keyCtx, ok := child.(*grammar.KeyContext); ok {
|
||||
// create a full text search condition on the body field
|
||||
|
||||
keyText := keyCtx.GetText()
|
||||
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(keyText), v.builder, v.startNs, v.endNs)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
return cond
|
||||
searchText = keyCtx.GetText()
|
||||
} else if valCtx, ok := child.(*grammar.ValueContext); ok {
|
||||
var text string
|
||||
if valCtx.QUOTED_TEXT() != nil {
|
||||
text = trimQuotes(valCtx.QUOTED_TEXT().GetText())
|
||||
searchText = trimQuotes(valCtx.QUOTED_TEXT().GetText())
|
||||
} else if valCtx.NUMBER() != nil {
|
||||
text = valCtx.NUMBER().GetText()
|
||||
searchText = valCtx.NUMBER().GetText()
|
||||
} else if valCtx.BOOL() != nil {
|
||||
text = valCtx.BOOL().GetText()
|
||||
searchText = valCtx.BOOL().GetText()
|
||||
} else if valCtx.KEY() != nil {
|
||||
text = valCtx.KEY().GetText()
|
||||
searchText = valCtx.KEY().GetText()
|
||||
} else {
|
||||
v.errors = append(v.errors, fmt.Sprintf("unsupported value type: %s", valCtx.GetText()))
|
||||
return ""
|
||||
}
|
||||
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(text), v.builder, v.startNs, v.endNs)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
return cond
|
||||
}
|
||||
cond, err := v.conditionBuilder.ConditionFor(context.Background(), v.fullTextColumn, qbtypes.FilterOperatorRegexp, FormatFullTextSearch(searchText), v.builder, v.startNs, v.endNs)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
return "" // Should not happen with valid input
|
||||
@@ -383,6 +376,7 @@ func (v *filterExpressionVisitor) VisitComparison(ctx *grammar.ComparisonContext
|
||||
for _, key := range keys {
|
||||
condition, err := v.conditionBuilder.ConditionFor(context.Background(), key, op, nil, v.builder, v.startNs, v.endNs)
|
||||
if err != nil {
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
conds = append(conds, condition)
|
||||
@@ -648,7 +642,6 @@ func (v *filterExpressionVisitor) VisitValueList(ctx *grammar.ValueListContext)
|
||||
|
||||
// VisitFullText handles standalone quoted strings for full-text search
|
||||
func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) any {
|
||||
|
||||
if v.skipFullTextFilter {
|
||||
return ""
|
||||
}
|
||||
@@ -670,6 +663,7 @@ func (v *filterExpressionVisitor) VisitFullText(ctx *grammar.FullTextContext) an
|
||||
v.errors = append(v.errors, fmt.Sprintf("failed to build full text search condition: %s", err.Error()))
|
||||
return ""
|
||||
}
|
||||
|
||||
return cond
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,12 @@ package telemetrylogs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
schema "github.com/SigNoz/signoz-otel-collector/cmd/signozschemamigrator/schema_migrator"
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/huandu/go-sqlbuilder"
|
||||
)
|
||||
@@ -35,7 +33,7 @@ func (c *conditionBuilder) conditionFor(
|
||||
return "", err
|
||||
}
|
||||
|
||||
if column.Type.GetType() == schema.ColumnTypeEnumJSON && querybuilder.BodyJSONQueryEnabled {
|
||||
if column.Type.GetType() == schema.ColumnTypeEnumJSON && querybuilder.BodyJSONQueryEnabled && key.Name != messageSubField {
|
||||
valueType, value := InferDataType(value, operator, key)
|
||||
cond, err := NewJSONConditionBuilder(key, valueType).buildJSONCondition(operator, value, sb)
|
||||
if err != nil {
|
||||
@@ -54,14 +52,14 @@ func (c *conditionBuilder) conditionFor(
|
||||
}
|
||||
|
||||
// Check if this is a body JSON search - either by FieldContext
|
||||
if key.FieldContext == telemetrytypes.FieldContextBody {
|
||||
if key.FieldContext == telemetrytypes.FieldContextBody && !querybuilder.BodyJSONQueryEnabled {
|
||||
tblFieldName, value = GetBodyJSONKey(ctx, key, operator, value)
|
||||
}
|
||||
|
||||
tblFieldName, value = querybuilder.DataTypeCollisionHandledFieldName(key, value, tblFieldName, operator)
|
||||
|
||||
// make use of case insensitive index for body
|
||||
if tblFieldName == "body" {
|
||||
if tblFieldName == "body" || tblFieldName == messageSubColumn {
|
||||
switch operator {
|
||||
case qbtypes.FilterOperatorLike:
|
||||
return sb.ILike(tblFieldName, value), nil
|
||||
@@ -108,7 +106,6 @@ func (c *conditionBuilder) conditionFor(
|
||||
return sb.ILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
||||
case qbtypes.FilterOperatorNotContains:
|
||||
return sb.NotILike(tblFieldName, fmt.Sprintf("%%%s%%", value)), nil
|
||||
|
||||
case qbtypes.FilterOperatorRegexp:
|
||||
// Note: Escape $$ to $$$$ to avoid sqlbuilder interpreting materialized $ signs
|
||||
// Only needed because we are using sprintf instead of sb.Match (not implemented in sqlbuilder)
|
||||
@@ -178,9 +175,8 @@ func (c *conditionBuilder) conditionFor(
|
||||
case schema.ColumnTypeEnumJSON:
|
||||
if operator == qbtypes.FilterOperatorExists {
|
||||
return sb.IsNotNull(tblFieldName), nil
|
||||
} else {
|
||||
return sb.IsNull(tblFieldName), nil
|
||||
}
|
||||
return sb.IsNull(tblFieldName), nil
|
||||
case schema.ColumnTypeEnumLowCardinality:
|
||||
switch elementType := column.Type.(schema.LowCardinalityColumnType).ElementType; elementType.GetType() {
|
||||
case schema.ColumnTypeEnumString:
|
||||
@@ -247,19 +243,30 @@ func (c *conditionBuilder) ConditionFor(
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !(key.FieldContext == telemetrytypes.FieldContextBody && querybuilder.BodyJSONQueryEnabled) && operator.AddDefaultExistsFilter() {
|
||||
// skip adding exists filter for intrinsic fields
|
||||
// with an exception for body json search
|
||||
field, _ := c.fm.FieldFor(ctx, key)
|
||||
if slices.Contains(maps.Keys(IntrinsicFields), field) && key.FieldContext != telemetrytypes.FieldContextBody {
|
||||
// Skip adding exists filter for intrinsic fields i.e. Table level log context fields
|
||||
buildExistCondition := operator.AddDefaultExistsFilter()
|
||||
switch key.FieldContext {
|
||||
case telemetrytypes.FieldContextLog, telemetrytypes.FieldContextScope:
|
||||
// pass; No need to build exist condition for top level columns
|
||||
// immediately return
|
||||
return condition, nil
|
||||
case telemetrytypes.FieldContextResource, telemetrytypes.FieldContextAttribute:
|
||||
// build exist condition for resource and attribute fields based on filter operator
|
||||
case telemetrytypes.FieldContextBody:
|
||||
// Querying JSON fields already account for Nullability of fields
|
||||
// so additional exists checks are not needed
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
return condition, nil
|
||||
}
|
||||
}
|
||||
|
||||
if buildExistCondition {
|
||||
existsCondition, err := c.conditionFor(ctx, key, qbtypes.FilterOperatorExists, nil, sb)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return sb.And(condition, existsCondition), nil
|
||||
}
|
||||
|
||||
return condition, nil
|
||||
}
|
||||
|
||||
@@ -127,7 +127,8 @@ func TestConditionFor(t *testing.T) {
|
||||
{
|
||||
name: "Contains operator - body",
|
||||
key: telemetrytypes.TelemetryFieldKey{
|
||||
Name: "body",
|
||||
Name: "body",
|
||||
FieldContext: telemetrytypes.FieldContextLog,
|
||||
},
|
||||
operator: qbtypes.FilterOperatorContains,
|
||||
value: 521509198310,
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package telemetrylogs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/SigNoz/signoz-otel-collector/constants"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
qbtypes "github.com/SigNoz/signoz/pkg/types/querybuildertypes/querybuildertypesv5"
|
||||
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
|
||||
)
|
||||
@@ -17,7 +20,7 @@ const (
|
||||
LogsV2TimestampColumn = "timestamp"
|
||||
LogsV2ObservedTimestampColumn = "observed_timestamp"
|
||||
LogsV2BodyColumn = "body"
|
||||
LogsV2BodyJSONColumn = constants.BodyV2Column
|
||||
LogsV2BodyV2Column = constants.BodyV2Column
|
||||
LogsV2BodyPromotedColumn = constants.BodyPromotedColumn
|
||||
LogsV2TraceIDColumn = "trace_id"
|
||||
LogsV2SpanIDColumn = "span_id"
|
||||
@@ -34,8 +37,14 @@ const (
|
||||
LogsV2ResourcesStringColumn = "resources_string"
|
||||
LogsV2ScopeStringColumn = "scope_string"
|
||||
|
||||
BodyJSONColumnPrefix = constants.BodyV2ColumnPrefix
|
||||
BodyV2ColumnPrefix = constants.BodyV2ColumnPrefix
|
||||
BodyPromotedColumnPrefix = constants.BodyPromotedColumnPrefix
|
||||
|
||||
// messageSubColumn is the ClickHouse sub-column that body searches map to
|
||||
// when BodyJSONQueryEnabled is true.
|
||||
messageSubField = "message"
|
||||
messageSubColumn = "body_v2.message"
|
||||
bodySearchDefaultWarning = "body searches default to `body.message:string`. Use `body.<key>` to search a different field inside body"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -118,3 +127,11 @@ var (
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func bodyAliasExpression() string {
|
||||
if !querybuilder.BodyJSONQueryEnabled {
|
||||
return LogsV2BodyColumn
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s as body", LogsV2BodyV2Column)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,8 @@ var (
|
||||
"severity_text": {Name: "severity_text", Type: schema.LowCardinalityColumnType{ElementType: schema.ColumnTypeString}},
|
||||
"severity_number": {Name: "severity_number", Type: schema.ColumnTypeUInt8},
|
||||
"body": {Name: "body", Type: schema.ColumnTypeString},
|
||||
LogsV2BodyJSONColumn: {Name: LogsV2BodyJSONColumn, Type: schema.JSONColumnType{
|
||||
messageSubColumn: {Name: messageSubColumn, Type: schema.ColumnTypeString},
|
||||
LogsV2BodyV2Column: {Name: LogsV2BodyV2Column, Type: schema.JSONColumnType{
|
||||
MaxDynamicTypes: utils.ToPointer(uint(32)),
|
||||
MaxDynamicPaths: utils.ToPointer(uint(0)),
|
||||
}},
|
||||
@@ -88,21 +89,26 @@ func (m *fieldMapper) getColumn(_ context.Context, key *telemetrytypes.Telemetry
|
||||
return logsV2Columns["attributes_bool"], nil
|
||||
}
|
||||
case telemetrytypes.FieldContextBody:
|
||||
// Body context is for JSON body fields
|
||||
// Use body_json if feature flag is enabled
|
||||
// Body context is for JSON body fields. Use body_v2 if feature flag is enabled.
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
return logsV2Columns[LogsV2BodyJSONColumn], nil
|
||||
if key.Name == messageSubField {
|
||||
return logsV2Columns[messageSubColumn], nil
|
||||
}
|
||||
return logsV2Columns[LogsV2BodyV2Column], nil
|
||||
}
|
||||
// Fall back to legacy body column
|
||||
return logsV2Columns["body"], nil
|
||||
case telemetrytypes.FieldContextLog, telemetrytypes.FieldContextUnspecified:
|
||||
if key.Name == LogsV2BodyColumn && querybuilder.BodyJSONQueryEnabled {
|
||||
return logsV2Columns[messageSubColumn], nil
|
||||
}
|
||||
col, ok := logsV2Columns[key.Name]
|
||||
if !ok {
|
||||
// check if the key has body JSON search
|
||||
if strings.HasPrefix(key.Name, telemetrytypes.BodyJSONStringSearchPrefix) {
|
||||
// Use body_json if feature flag is enabled and we have a body condition builder
|
||||
// Use body_v2 if feature flag is enabled and we have a body condition builder
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
return logsV2Columns[LogsV2BodyJSONColumn], nil
|
||||
return logsV2Columns[LogsV2BodyV2Column], nil
|
||||
}
|
||||
// Fall back to legacy body column
|
||||
return logsV2Columns["body"], nil
|
||||
@@ -138,6 +144,10 @@ func (m *fieldMapper) FieldFor(ctx context.Context, key *telemetrytypes.Telemetr
|
||||
}
|
||||
return fmt.Sprintf("multiIf(%s.`%s` IS NOT NULL, %s.`%s`::String, mapContains(%s, '%s'), %s, NULL)", column.Name, key.Name, column.Name, key.Name, oldColumn.Name, key.Name, oldKeyName), nil
|
||||
case telemetrytypes.FieldContextBody:
|
||||
if key.Name == messageSubField {
|
||||
return messageSubColumn, nil
|
||||
}
|
||||
|
||||
if key.JSONDataType == nil {
|
||||
return "", qbtypes.ErrColumnNotFound
|
||||
}
|
||||
@@ -246,34 +256,37 @@ func (m *fieldMapper) buildFieldForJSON(key *telemetrytypes.TelemetryFieldKey) (
|
||||
node := plan[0]
|
||||
|
||||
expr := fmt.Sprintf("dynamicElement(%s, '%s')", node.FieldPath(), node.TerminalConfig.ElemType.StringValue())
|
||||
if key.Materialized {
|
||||
if len(plan) < 2 {
|
||||
return "", errors.Newf(errors.TypeUnexpected, CodePromotedPlanMissing,
|
||||
"plan length is less than 2 for promoted path: %s", key.Name)
|
||||
}
|
||||
// TODO(Piyush): Promoted path logic commented out. Materialized now means type hint
|
||||
// promotion will be extracted from key field evolution
|
||||
// (direct sub-column access), not a promoted body_promoted.* column.
|
||||
// if key.Materialized {
|
||||
// if len(plan) < 2 {
|
||||
// return "", errors.Newf(errors.TypeUnexpected, CodePromotedPlanMissing,
|
||||
// "plan length is less than 2 for promoted path: %s", key.Name)
|
||||
// }
|
||||
|
||||
node := plan[1]
|
||||
promotedExpr := fmt.Sprintf(
|
||||
"dynamicElement(%s, '%s')",
|
||||
node.FieldPath(),
|
||||
node.TerminalConfig.ElemType.StringValue(),
|
||||
)
|
||||
// node := plan[1]
|
||||
// promotedExpr := fmt.Sprintf(
|
||||
// "dynamicElement(%s, '%s')",
|
||||
// node.FieldPath(),
|
||||
// node.TerminalConfig.ElemType.StringValue(),
|
||||
// )
|
||||
|
||||
// dynamicElement returns NULL for scalar types or an empty array for array types.
|
||||
if node.TerminalConfig.ElemType.IsArray {
|
||||
expr = fmt.Sprintf(
|
||||
"if(length(%s) > 0, %s, %s)",
|
||||
promotedExpr,
|
||||
promotedExpr,
|
||||
expr,
|
||||
)
|
||||
} else {
|
||||
// promoted column first then body_json column
|
||||
// TODO(Piyush): Change this in future for better performance
|
||||
expr = fmt.Sprintf("coalesce(%s, %s)", promotedExpr, expr)
|
||||
}
|
||||
// // dynamicElement returns NULL for scalar types or an empty array for array types.
|
||||
// if node.TerminalConfig.ElemType.IsArray {
|
||||
// expr = fmt.Sprintf(
|
||||
// "if(length(%s) > 0, %s, %s)",
|
||||
// promotedExpr,
|
||||
// promotedExpr,
|
||||
// expr,
|
||||
// )
|
||||
// } else {
|
||||
// // promoted column first then body_json column
|
||||
// // TODO(Piyush): Change this in future for better performance
|
||||
// expr = fmt.Sprintf("coalesce(%s, %s)", promotedExpr, expr)
|
||||
// }
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewJSONConditionBuilder(key *telemetrytypes.TelemetryFieldKey, valueType te
|
||||
return &jsonConditionBuilder{key: key, valueType: telemetrytypes.MappingFieldDataTypeToJSONDataType[valueType]}
|
||||
}
|
||||
|
||||
// BuildCondition builds the full WHERE condition for body_json JSON paths
|
||||
// BuildCondition builds the full WHERE condition for body_v2 JSON paths
|
||||
func (c *jsonConditionBuilder) buildJSONCondition(operator qbtypes.FilterOperator, value any, sb *sqlbuilder.SelectBuilder) (string, error) {
|
||||
conditions := []string{}
|
||||
for _, node := range c.key.JSONPlan {
|
||||
@@ -40,6 +40,7 @@ func (c *jsonConditionBuilder) buildJSONCondition(operator qbtypes.FilterOperato
|
||||
}
|
||||
conditions = append(conditions, condition)
|
||||
}
|
||||
|
||||
return sb.Or(conditions...), nil
|
||||
}
|
||||
|
||||
@@ -288,9 +289,9 @@ func (c *jsonConditionBuilder) applyOperator(sb *sqlbuilder.SelectBuilder, field
|
||||
}
|
||||
return sb.NotIn(fieldExpr, values...), nil
|
||||
case qbtypes.FilterOperatorExists:
|
||||
return fmt.Sprintf("%s IS NOT NULL", fieldExpr), nil
|
||||
return sb.IsNotNull(fieldExpr), nil
|
||||
case qbtypes.FilterOperatorNotExists:
|
||||
return fmt.Sprintf("%s IS NULL", fieldExpr), nil
|
||||
return sb.IsNull(fieldExpr), nil
|
||||
// between and not between
|
||||
case qbtypes.FilterOperatorBetween, qbtypes.FilterOperatorNotBetween:
|
||||
values, ok := value.([]any)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -65,7 +65,7 @@ func (b *logQueryStatementBuilder) Build(
|
||||
start = querybuilder.ToNanoSecs(start)
|
||||
end = querybuilder.ToNanoSecs(end)
|
||||
|
||||
keySelectors := getKeySelectors(query)
|
||||
keySelectors, warnings := getKeySelectors(query)
|
||||
keys, _, err := b.metadataStore.GetKeysMulti(ctx, keySelectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -76,20 +76,29 @@ func (b *logQueryStatementBuilder) Build(
|
||||
// Create SQL builder
|
||||
q := sqlbuilder.NewSelectBuilder()
|
||||
|
||||
var stmt *qbtypes.Statement
|
||||
switch requestType {
|
||||
case qbtypes.RequestTypeRaw, qbtypes.RequestTypeRawStream:
|
||||
return b.buildListQuery(ctx, q, query, start, end, keys, variables)
|
||||
stmt, err = b.buildListQuery(ctx, q, query, start, end, keys, variables)
|
||||
case qbtypes.RequestTypeTimeSeries:
|
||||
return b.buildTimeSeriesQuery(ctx, q, query, start, end, keys, variables)
|
||||
stmt, err = b.buildTimeSeriesQuery(ctx, q, query, start, end, keys, variables)
|
||||
case qbtypes.RequestTypeScalar:
|
||||
return b.buildScalarQuery(ctx, q, query, start, end, keys, false, variables)
|
||||
stmt, err = b.buildScalarQuery(ctx, q, query, start, end, keys, false, variables)
|
||||
default:
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported request type: %s", requestType)
|
||||
}
|
||||
|
||||
return nil, errors.NewInvalidInputf(errors.CodeInvalidInput, "unsupported request type: %s", requestType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stmt.Warnings = append(stmt.Warnings, warnings...)
|
||||
return stmt, nil
|
||||
}
|
||||
|
||||
func getKeySelectors(query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]) []*telemetrytypes.FieldKeySelector {
|
||||
func getKeySelectors(query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]) ([]*telemetrytypes.FieldKeySelector, []string) {
|
||||
var keySelectors []*telemetrytypes.FieldKeySelector
|
||||
var warnings []string
|
||||
|
||||
for idx := range query.Aggregations {
|
||||
aggExpr := query.Aggregations[idx]
|
||||
@@ -136,7 +145,19 @@ func getKeySelectors(query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]) []
|
||||
keySelectors[idx].SelectorMatchType = telemetrytypes.FieldSelectorMatchTypeExact
|
||||
}
|
||||
|
||||
return keySelectors
|
||||
// When the new JSON body experience is enabled, warn the user if they use the bare
|
||||
// "body" key in the filter — queries on plain "body" default to body.message:string.
|
||||
// TODO(Piyush): Setup better for coming FTS support.
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
for _, sel := range keySelectors {
|
||||
if sel.Name == LogsV2BodyColumn {
|
||||
warnings = append(warnings, bodySearchDefaultWarning)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return keySelectors, warnings
|
||||
}
|
||||
|
||||
func (b *logQueryStatementBuilder) adjustKeys(ctx context.Context, keys map[string][]*telemetrytypes.TelemetryFieldKey, query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation], requestType qbtypes.RequestType) qbtypes.QueryBuilderQuery[qbtypes.LogAggregation] {
|
||||
@@ -203,7 +224,6 @@ func (b *logQueryStatementBuilder) adjustKeys(ctx context.Context, keys map[stri
|
||||
}
|
||||
|
||||
func (b *logQueryStatementBuilder) adjustKey(key *telemetrytypes.TelemetryFieldKey, keys map[string][]*telemetrytypes.TelemetryFieldKey) []string {
|
||||
|
||||
// First check if it matches with any intrinsic fields
|
||||
var intrinsicOrCalculatedField telemetrytypes.TelemetryFieldKey
|
||||
if _, ok := IntrinsicFields[key.Name]; ok {
|
||||
@@ -212,7 +232,6 @@ func (b *logQueryStatementBuilder) adjustKey(key *telemetrytypes.TelemetryFieldK
|
||||
}
|
||||
|
||||
return querybuilder.AdjustKey(key, keys, nil)
|
||||
|
||||
}
|
||||
|
||||
// buildListQuery builds a query for list panel type
|
||||
@@ -249,11 +268,7 @@ func (b *logQueryStatementBuilder) buildListQuery(
|
||||
sb.SelectMore(LogsV2SeverityNumberColumn)
|
||||
sb.SelectMore(LogsV2ScopeNameColumn)
|
||||
sb.SelectMore(LogsV2ScopeVersionColumn)
|
||||
sb.SelectMore(LogsV2BodyColumn)
|
||||
if querybuilder.BodyJSONQueryEnabled {
|
||||
sb.SelectMore(LogsV2BodyJSONColumn)
|
||||
sb.SelectMore(LogsV2BodyPromotedColumn)
|
||||
}
|
||||
sb.SelectMore(bodyAliasExpression())
|
||||
sb.SelectMore(LogsV2AttributesStringColumn)
|
||||
sb.SelectMore(LogsV2AttributesNumberColumn)
|
||||
sb.SelectMore(LogsV2AttributesBoolColumn)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder"
|
||||
"github.com/SigNoz/signoz/pkg/querybuilder/resourcefilter"
|
||||
@@ -886,3 +887,246 @@ func TestAdjustKey(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStmtBuilderBodyField(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
requestType qbtypes.RequestType
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]
|
||||
enableBodyJSONQuery bool
|
||||
expected qbtypes.Statement
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "body_exists",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body Exists"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: true,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body_v2 as body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND body_v2.message <> ? AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
Warnings: []string{bodySearchDefaultWarning},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_exists_disabled",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body Exists"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: false,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND body <> ? AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_empty",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body == ''"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: true,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body_v2 as body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND body_v2.message = ? AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
Warnings: []string{bodySearchDefaultWarning},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_empty_disabled",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body == ''"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: false,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND body = ? AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_contains",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body CONTAINS 'error'"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: true,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body_v2 as body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND LOWER(body_v2.message) LIKE LOWER(?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "%error%", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
Warnings: []string{bodySearchDefaultWarning},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_contains_disabled",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "body CONTAINS 'error'"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: false,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND LOWER(body) LIKE LOWER(?) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "%error%", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
defer disable()
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if c.enableBodyJSONQuery {
|
||||
enable()
|
||||
} else {
|
||||
disable()
|
||||
}
|
||||
// build the key map after enabling/disabling body JSON query
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
for _, field := range IntrinsicFields {
|
||||
f := field
|
||||
mockMetadataStore.KeysMap[field.Name] = append(mockMetadataStore.KeysMap[field.Name], &f)
|
||||
}
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
resourceFilterStmtBuilder := resourceFilterStmtBuilder()
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
mockMetadataStore,
|
||||
fm,
|
||||
cb,
|
||||
resourceFilterStmtBuilder,
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
)
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErr.Error())
|
||||
} else {
|
||||
if err != nil {
|
||||
_, _, _, _, _, add := errors.Unwrapb(err)
|
||||
t.Logf("error additionals: %v", add)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.expected.Query, q.Query)
|
||||
require.Equal(t, c.expected.Args, q.Args)
|
||||
require.Equal(t, c.expected.Warnings, q.Warnings)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStmtBuilderBodyFullTextSearch(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
requestType qbtypes.RequestType
|
||||
query qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]
|
||||
enableBodyJSONQuery bool
|
||||
expected qbtypes.Statement
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "body_contains",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "'error'"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: true,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body_v2 as body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND match(LOWER(body_v2.message), LOWER(?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "error", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "body_contains_disabled",
|
||||
requestType: qbtypes.RequestTypeRaw,
|
||||
query: qbtypes.QueryBuilderQuery[qbtypes.LogAggregation]{
|
||||
Signal: telemetrytypes.SignalLogs,
|
||||
Filter: &qbtypes.Filter{Expression: "'error'"},
|
||||
Limit: 10,
|
||||
},
|
||||
enableBodyJSONQuery: false,
|
||||
expected: qbtypes.Statement{
|
||||
Query: "WITH __resource_filter AS (SELECT fingerprint FROM signoz_logs.distributed_logs_v2_resource WHERE true AND seen_at_ts_bucket_start >= ? AND seen_at_ts_bucket_start <= ?) SELECT timestamp, id, trace_id, span_id, trace_flags, severity_text, severity_number, scope_name, scope_version, body, attributes_string, attributes_number, attributes_bool, resources_string, scope_string FROM signoz_logs.distributed_logs_v2 WHERE resource_fingerprint GLOBAL IN (SELECT fingerprint FROM __resource_filter) AND match(LOWER(body), LOWER(?)) AND timestamp >= ? AND ts_bucket_start >= ? AND timestamp < ? AND ts_bucket_start <= ? LIMIT ?",
|
||||
Args: []any{uint64(1747945619), uint64(1747983448), "error", "1747947419000000000", uint64(1747945619), "1747983448000000000", uint64(1747983448), 10},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
fm := NewFieldMapper()
|
||||
cb := NewConditionBuilder(fm)
|
||||
|
||||
enable, disable := jsonQueryTestUtil(t)
|
||||
defer disable()
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
if c.enableBodyJSONQuery {
|
||||
enable()
|
||||
} else {
|
||||
disable()
|
||||
}
|
||||
// build the key map after enabling/disabling body JSON query
|
||||
mockMetadataStore := telemetrytypestest.NewMockMetadataStore()
|
||||
for _, field := range IntrinsicFields {
|
||||
f := field
|
||||
mockMetadataStore.KeysMap[field.Name] = append(mockMetadataStore.KeysMap[field.Name], &f)
|
||||
}
|
||||
aggExprRewriter := querybuilder.NewAggExprRewriter(instrumentationtest.New().ToProviderSettings(), nil, fm, cb, nil)
|
||||
resourceFilterStmtBuilder := resourceFilterStmtBuilder()
|
||||
statementBuilder := NewLogQueryStatementBuilder(
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
mockMetadataStore,
|
||||
fm,
|
||||
cb,
|
||||
resourceFilterStmtBuilder,
|
||||
aggExprRewriter,
|
||||
DefaultFullTextColumn,
|
||||
GetBodyJSONKey,
|
||||
)
|
||||
|
||||
q, err := statementBuilder.Build(context.Background(), 1747947419000, 1747983448000, c.requestType, c.query, nil)
|
||||
if c.expectedErr != nil {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), c.expectedErr.Error())
|
||||
} else {
|
||||
if err != nil {
|
||||
_, _, _, _, _, add := errors.Unwrapb(err)
|
||||
t.Logf("error additionals: %v", add)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, c.expected.Query, q.Query)
|
||||
require.Equal(t, c.expected.Args, q.Args)
|
||||
require.Equal(t, c.expected.Warnings, q.Warnings)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,6 @@ func buildCompleteFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
"body": {
|
||||
{
|
||||
Name: "body",
|
||||
FieldContext: telemetrytypes.FieldContextLog,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
"http.status_code": {
|
||||
{
|
||||
Name: "http.status_code",
|
||||
@@ -938,6 +931,13 @@ func buildCompleteFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
||||
Materialized: true,
|
||||
},
|
||||
},
|
||||
"body": {
|
||||
{
|
||||
Name: "body",
|
||||
FieldContext: telemetrytypes.FieldContextLog,
|
||||
FieldDataType: telemetrytypes.FieldDataTypeString,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, keys := range keysMap {
|
||||
@@ -945,6 +945,7 @@ func buildCompleteFieldKeyMap() map[string][]*telemetrytypes.TelemetryFieldKey {
|
||||
key.Signal = telemetrytypes.SignalLogs
|
||||
}
|
||||
}
|
||||
|
||||
return keysMap
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ func (t *telemetryMetaStore) fetchBodyJSONPaths(ctx context.Context,
|
||||
instrumentationtypes.CodeNamespace: "metadata",
|
||||
instrumentationtypes.CodeFunctionName: "fetchBodyJSONPaths",
|
||||
})
|
||||
|
||||
query, args, limit := buildGetBodyJSONPathsQuery(fieldKeySelectors)
|
||||
rows, err := t.telemetrystore.ClickhouseDB().Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
@@ -184,7 +185,6 @@ func buildGetBodyJSONPathsQuery(fieldKeySelectors []*telemetrytypes.FieldKeySele
|
||||
limit += fieldKeySelector.Limit
|
||||
}
|
||||
sb.Where(sb.Or(orClauses...))
|
||||
|
||||
// Group by path to get unique paths with aggregated types
|
||||
sb.GroupBy("path")
|
||||
|
||||
@@ -319,7 +319,7 @@ func (t *telemetryMetaStore) ListJSONValues(ctx context.Context, path string, li
|
||||
if promoted {
|
||||
path = telemetrylogs.BodyPromotedColumnPrefix + path
|
||||
} else {
|
||||
path = telemetrylogs.BodyJSONColumnPrefix + path
|
||||
path = telemetrylogs.BodyV2ColumnPrefix + path
|
||||
}
|
||||
|
||||
from := fmt.Sprintf("%s.%s", telemetrylogs.DBName, telemetrylogs.LogsV2TableName)
|
||||
@@ -522,7 +522,7 @@ func (t *telemetryMetaStore) GetPromotedPaths(ctx context.Context, paths ...stri
|
||||
// TODO(Piyush): Remove this function
|
||||
func CleanPathPrefixes(path string) string {
|
||||
path = strings.TrimPrefix(path, telemetrytypes.BodyJSONStringSearchPrefix)
|
||||
path = strings.TrimPrefix(path, telemetrylogs.BodyJSONColumnPrefix)
|
||||
path = strings.TrimPrefix(path, telemetrylogs.BodyV2ColumnPrefix)
|
||||
path = strings.TrimPrefix(path, telemetrylogs.BodyPromotedColumnPrefix)
|
||||
return path
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ func NewTelemetryMetaStore(
|
||||
jsonColumnMetadata: map[telemetrytypes.Signal]map[telemetrytypes.FieldContext]telemetrytypes.JSONColumnMetadata{
|
||||
telemetrytypes.SignalLogs: {
|
||||
telemetrytypes.FieldContextBody: telemetrytypes.JSONColumnMetadata{
|
||||
BaseColumn: telemetrylogs.LogsV2BodyJSONColumn,
|
||||
BaseColumn: telemetrylogs.LogsV2BodyV2Column,
|
||||
PromotedColumn: telemetrylogs.LogsV2BodyPromotedColumn,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ var (
|
||||
IdentNProviderTokenizer = IdentNProvider{valuer.NewString("tokenizer")}
|
||||
IdentNProviderAPIkey = IdentNProvider{valuer.NewString("api_key")}
|
||||
IdentNProviderAnonymous = IdentNProvider{valuer.NewString("anonymous")}
|
||||
IdentNProviderInternal = IdentNProvider{valuer.NewString("internal")}
|
||||
)
|
||||
|
||||
type IdentNProvider struct{ valuer.String }
|
||||
|
||||
@@ -13,9 +13,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCodeCloudIntegrationNotFound = errors.MustNewCode("cloud_integration_not_found")
|
||||
ErrCodeCloudIntegrationAlreadyExists = errors.MustNewCode("cloud_integration_already_exists")
|
||||
ErrCodeCloudIntegrationServiceAlreadyExists = errors.MustNewCode("cloud_integration_service_already_exists")
|
||||
ErrCodeCloudIntegrationNotFound = errors.MustNewCode("cloud_integration_not_found")
|
||||
)
|
||||
|
||||
// StorableCloudIntegration represents a cloud integration stored in the database.
|
||||
|
||||
@@ -10,12 +10,6 @@ type Store interface {
|
||||
// GetAccountByID returns a cloud integration account by id
|
||||
GetAccountByID(ctx context.Context, orgID, id valuer.UUID, provider CloudProviderType) (*StorableCloudIntegration, error)
|
||||
|
||||
// GetConnectedAccount for a given provider
|
||||
GetConnectedAccount(ctx context.Context, orgID valuer.UUID, provider CloudProviderType, providerAccountID string) (*StorableCloudIntegration, error)
|
||||
|
||||
// ListConnectedAccounts returns all the cloud integration accounts for the org and cloud provider
|
||||
ListConnectedAccounts(ctx context.Context, orgID valuer.UUID, provider CloudProviderType) ([]*StorableCloudIntegration, error)
|
||||
|
||||
// CreateAccount creates a new cloud integration account
|
||||
CreateAccount(ctx context.Context, account *StorableCloudIntegration) (*StorableCloudIntegration, error)
|
||||
|
||||
@@ -25,17 +19,23 @@ type Store interface {
|
||||
// RemoveAccount marks a cloud integration account as removed by setting the RemovedAt field
|
||||
RemoveAccount(ctx context.Context, orgID, id valuer.UUID, provider CloudProviderType) error
|
||||
|
||||
// ListConnectedAccounts returns all the cloud integration accounts for the org and cloud provider
|
||||
ListConnectedAccounts(ctx context.Context, orgID valuer.UUID, provider CloudProviderType) ([]*StorableCloudIntegration, error)
|
||||
|
||||
// GetConnectedAccount for a given provider
|
||||
GetConnectedAccount(ctx context.Context, orgID valuer.UUID, provider CloudProviderType, providerAccountID string) (*StorableCloudIntegration, error)
|
||||
|
||||
// cloud_integration_service related methods
|
||||
|
||||
// GetServiceByServiceID returns the cloud integration service for the given cloud integration id and service id
|
||||
GetServiceByServiceID(ctx context.Context, cloudIntegrationID valuer.UUID, serviceID ServiceID) (*StorableCloudIntegrationService, error)
|
||||
|
||||
// ListServices returns all the cloud integration services for the given cloud integration id
|
||||
ListServices(ctx context.Context, cloudIntegrationID valuer.UUID) ([]*StorableCloudIntegrationService, error)
|
||||
|
||||
// CreateService creates a new cloud integration service
|
||||
CreateService(ctx context.Context, service *StorableCloudIntegrationService) (*StorableCloudIntegrationService, error)
|
||||
|
||||
// UpdateService updates an existing cloud integration service
|
||||
UpdateService(ctx context.Context, service *StorableCloudIntegrationService) error
|
||||
|
||||
// ListServices returns all the cloud integration services for the given cloud integration id
|
||||
ListServices(ctx context.Context, cloudIntegrationID valuer.UUID) ([]*StorableCloudIntegrationService, error)
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ type JSONAccessNode struct {
|
||||
// Node information
|
||||
Name string
|
||||
IsTerminal bool
|
||||
isRoot bool // marked true for only body_json and body_json_promoted
|
||||
isRoot bool // marked true for only body_v2 and body_promoted
|
||||
|
||||
// Precomputed type information (single source of truth)
|
||||
AvailableTypes []JSONDataType
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
package telemetrytypes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
otelconstants "github.com/SigNoz/signoz-otel-collector/constants"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
bodyV2Column = otelconstants.BodyV2Column
|
||||
bodyPromotedColumn = otelconstants.BodyPromotedColumn
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions for Test Data Creation
|
||||
// ============================================================================
|
||||
@@ -109,8 +116,8 @@ func TestNode_Alias(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Root node returns name as-is",
|
||||
node: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
expected: "body_json",
|
||||
node: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
expected: bodyV2Column,
|
||||
},
|
||||
{
|
||||
name: "Node without parent returns backticked name",
|
||||
@@ -124,9 +131,9 @@ func TestNode_Alias(t *testing.T) {
|
||||
name: "Node with root parent uses dot separator",
|
||||
node: &JSONAccessNode{
|
||||
Name: "age",
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
},
|
||||
expected: "`" + "body_json" + ".age`",
|
||||
expected: "`" + bodyV2Column + ".age`",
|
||||
},
|
||||
{
|
||||
name: "Node with non-root parent uses array separator",
|
||||
@@ -134,10 +141,10 @@ func TestNode_Alias(t *testing.T) {
|
||||
Name: "name",
|
||||
Parent: &JSONAccessNode{
|
||||
Name: "education",
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
},
|
||||
},
|
||||
expected: "`" + "body_json" + ".education[].name`",
|
||||
expected: "`" + bodyV2Column + ".education[].name`",
|
||||
},
|
||||
{
|
||||
name: "Nested array path with multiple levels",
|
||||
@@ -147,11 +154,11 @@ func TestNode_Alias(t *testing.T) {
|
||||
Name: "awards",
|
||||
Parent: &JSONAccessNode{
|
||||
Name: "education",
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "`" + "body_json" + ".education[].awards[].type`",
|
||||
expected: "`" + bodyV2Column + ".education[].awards[].type`",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -173,18 +180,18 @@ func TestNode_FieldPath(t *testing.T) {
|
||||
name: "Simple field path from root",
|
||||
node: &JSONAccessNode{
|
||||
Name: "user",
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
},
|
||||
// FieldPath() always wraps the field name in backticks
|
||||
expected: "body_json" + ".`user`",
|
||||
expected: bodyV2Column + ".`user`",
|
||||
},
|
||||
{
|
||||
name: "Field path with backtick-required key",
|
||||
node: &JSONAccessNode{
|
||||
Name: "user-name", // requires backtick
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
},
|
||||
expected: "body_json" + ".`user-name`",
|
||||
expected: bodyV2Column + ".`user-name`",
|
||||
},
|
||||
{
|
||||
name: "Nested field path",
|
||||
@@ -192,11 +199,11 @@ func TestNode_FieldPath(t *testing.T) {
|
||||
Name: "age",
|
||||
Parent: &JSONAccessNode{
|
||||
Name: "user",
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
},
|
||||
},
|
||||
// FieldPath() always wraps the field name in backticks
|
||||
expected: "`" + "body_json" + ".user`.`age`",
|
||||
expected: "`" + bodyV2Column + ".user`.`age`",
|
||||
},
|
||||
{
|
||||
name: "Array element field path",
|
||||
@@ -204,11 +211,11 @@ func TestNode_FieldPath(t *testing.T) {
|
||||
Name: "name",
|
||||
Parent: &JSONAccessNode{
|
||||
Name: "education",
|
||||
Parent: NewRootJSONAccessNode("body_json", 32, 0),
|
||||
Parent: NewRootJSONAccessNode(bodyV2Column, 32, 0),
|
||||
},
|
||||
},
|
||||
// FieldPath() always wraps the field name in backticks
|
||||
expected: "`" + "body_json" + ".education`.`name`",
|
||||
expected: "`" + bodyV2Column + ".education`.`name`",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -236,36 +243,36 @@ func TestPlanJSON_BasicStructure(t *testing.T) {
|
||||
{
|
||||
name: "Simple path not promoted",
|
||||
key: makeKey("user.name", String, false),
|
||||
expectedYAML: `
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
- name: user.name
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- String
|
||||
maxDynamicTypes: 16
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`,
|
||||
`, bodyV2Column),
|
||||
},
|
||||
{
|
||||
name: "Simple path promoted",
|
||||
key: makeKey("user.name", String, true),
|
||||
expectedYAML: `
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
- name: user.name
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- String
|
||||
maxDynamicTypes: 16
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
- name: user.name
|
||||
column: body_json_promoted
|
||||
column: %s
|
||||
availableTypes:
|
||||
- String
|
||||
maxDynamicTypes: 16
|
||||
maxDynamicPaths: 256
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`,
|
||||
`, bodyV2Column, bodyPromotedColumn),
|
||||
},
|
||||
{
|
||||
name: "Empty path returns error",
|
||||
@@ -278,8 +285,8 @@ func TestPlanJSON_BasicStructure(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
}, types)
|
||||
if tt.expectErr {
|
||||
require.Error(t, err)
|
||||
@@ -304,9 +311,9 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
{
|
||||
name: "Single array level - JSON branch only",
|
||||
path: "education[].name",
|
||||
expectedYAML: `
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
- name: education
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -318,14 +325,14 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
maxDynamicTypes: 8
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`,
|
||||
`, bodyV2Column),
|
||||
},
|
||||
{
|
||||
name: "Single array level - both JSON and Dynamic branches",
|
||||
path: "education[].awards[].type",
|
||||
expectedYAML: `
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
- name: education
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -352,14 +359,14 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
maxDynamicPaths: 256
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`,
|
||||
`, bodyV2Column),
|
||||
},
|
||||
{
|
||||
name: "Deeply nested array path",
|
||||
path: "interests[].entities[].reviews[].entries[].metadata[].positions[].name",
|
||||
expectedYAML: `
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
- name: interests
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -399,14 +406,14 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
- String
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`,
|
||||
`, bodyV2Column),
|
||||
},
|
||||
{
|
||||
name: "ArrayAnyIndex replacement [*] to []",
|
||||
path: "education[*].name",
|
||||
expectedYAML: `
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
- name: education
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -418,7 +425,7 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
maxDynamicTypes: 8
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`,
|
||||
`, bodyV2Column),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -426,8 +433,8 @@ func TestPlanJSON_ArrayPaths(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
key := makeKey(tt.path, String, false)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
}, types)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, key.JSONPlan)
|
||||
@@ -445,15 +452,15 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
t.Run("Non-promoted plan", func(t *testing.T) {
|
||||
key := makeKey(path, String, false)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
}, types)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, key.JSONPlan, 1)
|
||||
|
||||
expectedYAML := `
|
||||
expectedYAML := fmt.Sprintf(`
|
||||
- name: education
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -480,7 +487,7 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
maxDynamicPaths: 256
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`
|
||||
`, bodyV2Column)
|
||||
got := plansToYAML(t, key.JSONPlan)
|
||||
require.YAMLEq(t, expectedYAML, got)
|
||||
})
|
||||
@@ -488,15 +495,15 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
t.Run("Promoted plan", func(t *testing.T) {
|
||||
key := makeKey(path, String, true)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
}, types)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, key.JSONPlan, 2)
|
||||
|
||||
expectedYAML := `
|
||||
expectedYAML := fmt.Sprintf(`
|
||||
- name: education
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -524,7 +531,7 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
- name: education
|
||||
column: body_json_promoted
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -554,7 +561,7 @@ func TestPlanJSON_PromotedVsNonPromoted(t *testing.T) {
|
||||
maxDynamicPaths: 256
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`
|
||||
`, bodyV2Column, bodyPromotedColumn)
|
||||
got := plansToYAML(t, key.JSONPlan)
|
||||
require.YAMLEq(t, expectedYAML, got)
|
||||
})
|
||||
@@ -575,11 +582,11 @@ func TestPlanJSON_EdgeCases(t *testing.T) {
|
||||
expectErr: true,
|
||||
},
|
||||
{
|
||||
name: "Very deep nesting - validates progression doesn't go negative",
|
||||
path: "interests[].entities[].reviews[].entries[].metadata[].positions[].name",
|
||||
expectedYAML: `
|
||||
name: "Very deep nesting - validates progression doesn't go negative",
|
||||
path: "interests[].entities[].reviews[].entries[].metadata[].positions[].name",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
- name: interests
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -619,14 +626,14 @@ func TestPlanJSON_EdgeCases(t *testing.T) {
|
||||
- String
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`,
|
||||
`, bodyV2Column),
|
||||
},
|
||||
{
|
||||
name: "Path with mixed scalar and array types",
|
||||
path: "education[].type",
|
||||
expectedYAML: `
|
||||
name: "Path with mixed scalar and array types",
|
||||
path: "education[].type",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
- name: education
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -639,20 +646,20 @@ func TestPlanJSON_EdgeCases(t *testing.T) {
|
||||
maxDynamicTypes: 8
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`,
|
||||
`, bodyV2Column),
|
||||
},
|
||||
{
|
||||
name: "Exists with only array types available",
|
||||
path: "education",
|
||||
expectedYAML: `
|
||||
name: "Exists with only array types available",
|
||||
path: "education",
|
||||
expectedYAML: fmt.Sprintf(`
|
||||
- name: education
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
isTerminal: true
|
||||
elemType: Array(JSON)
|
||||
`,
|
||||
`, bodyV2Column),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -668,8 +675,8 @@ func TestPlanJSON_EdgeCases(t *testing.T) {
|
||||
}
|
||||
key := makeKey(tt.path, keyType, false)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
}, types)
|
||||
if tt.expectErr {
|
||||
require.Error(t, err)
|
||||
@@ -687,15 +694,15 @@ func TestPlanJSON_TreeStructure(t *testing.T) {
|
||||
path := "education[].awards[].participated[].team[].branch"
|
||||
key := makeKey(path, String, false)
|
||||
err := key.SetJSONAccessPlan(JSONColumnMetadata{
|
||||
BaseColumn: "body_json",
|
||||
PromotedColumn: "body_json_promoted",
|
||||
BaseColumn: bodyV2Column,
|
||||
PromotedColumn: bodyPromotedColumn,
|
||||
}, types)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, key.JSONPlan, 1)
|
||||
|
||||
expectedYAML := `
|
||||
expectedYAML := fmt.Sprintf(`
|
||||
- name: education
|
||||
column: body_json
|
||||
column: %s
|
||||
availableTypes:
|
||||
- Array(JSON)
|
||||
maxDynamicTypes: 16
|
||||
@@ -780,7 +787,7 @@ func TestPlanJSON_TreeStructure(t *testing.T) {
|
||||
maxDynamicPaths: 64
|
||||
isTerminal: true
|
||||
elemType: String
|
||||
`
|
||||
`, bodyV2Column)
|
||||
|
||||
got := plansToYAML(t, key.JSONPlan)
|
||||
require.YAMLEq(t, expectedYAML, got)
|
||||
|
||||
@@ -20,9 +20,11 @@ type MockMetadataStore struct {
|
||||
PromotedPathsMap map[string]bool
|
||||
LogsJSONIndexesMap map[string][]schemamigrator.Index
|
||||
LookupKeysMap map[telemetrytypes.MetricMetadataLookupKey]int64
|
||||
// StaticFields holds signal-specific intrinsic field definitions (e.g. telemetrylogs.IntrinsicFields).
|
||||
StaticFields map[string]telemetrytypes.TelemetryFieldKey
|
||||
}
|
||||
|
||||
// NewMockMetadataStore creates a new instance of MockMetadataStore with initialized maps
|
||||
// NewMockMetadataStore creates a new instance of MockMetadataStore with initialized maps.
|
||||
func NewMockMetadataStore() *MockMetadataStore {
|
||||
return &MockMetadataStore{
|
||||
KeysMap: make(map[string][]*telemetrytypes.TelemetryFieldKey),
|
||||
@@ -33,12 +35,20 @@ func NewMockMetadataStore() *MockMetadataStore {
|
||||
PromotedPathsMap: make(map[string]bool),
|
||||
LogsJSONIndexesMap: make(map[string][]schemamigrator.Index),
|
||||
LookupKeysMap: make(map[telemetrytypes.MetricMetadataLookupKey]int64),
|
||||
StaticFields: make(map[string]telemetrytypes.TelemetryFieldKey),
|
||||
}
|
||||
}
|
||||
|
||||
// SetStaticFields sets the static fields for the mock metadata store.
|
||||
// Pass the signal-specific intrinsic fields (e.g. telemetrylogs.IntrinsicFields) so the mock
|
||||
// mirrors what the real metadata store does when injecting those definitions into key results.
|
||||
func (m *MockMetadataStore) SetStaticFields(intrinsicFields map[string]telemetrytypes.TelemetryFieldKey) {
|
||||
m.StaticFields = intrinsicFields
|
||||
}
|
||||
|
||||
// GetKeys returns a map of field keys types.TelemetryFieldKey by name
|
||||
func (m *MockMetadataStore) GetKeys(ctx context.Context, fieldKeySelector *telemetrytypes.FieldKeySelector) (map[string][]*telemetrytypes.TelemetryFieldKey, bool, error) {
|
||||
|
||||
setOfKeys := make(map[string]*telemetrytypes.TelemetryFieldKey)
|
||||
result := make(map[string][]*telemetrytypes.TelemetryFieldKey)
|
||||
|
||||
// If selector is nil, return all keys
|
||||
@@ -46,18 +56,30 @@ func (m *MockMetadataStore) GetKeys(ctx context.Context, fieldKeySelector *telem
|
||||
return m.KeysMap, true, nil
|
||||
}
|
||||
|
||||
// Apply selector logic
|
||||
// Apply selector logic from KeysMap
|
||||
for name, keys := range m.KeysMap {
|
||||
// Check if name matches
|
||||
if matchesName(fieldKeySelector, name) {
|
||||
filteredKeys := []*telemetrytypes.TelemetryFieldKey{}
|
||||
for _, key := range keys {
|
||||
if matchesKey(fieldKeySelector, key) {
|
||||
filteredKeys = append(filteredKeys, key)
|
||||
if _, exists := setOfKeys[key.Text()]; !exists {
|
||||
result[name] = append(result[name], key)
|
||||
setOfKeys[key.Text()] = key
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(filteredKeys) > 0 {
|
||||
result[name] = filteredKeys
|
||||
}
|
||||
}
|
||||
|
||||
// StaticFields (e.g. IntrinsicFields), mirroring the real metadata store.
|
||||
for key, field := range m.StaticFields {
|
||||
if !matchesName(fieldKeySelector, key) {
|
||||
continue
|
||||
}
|
||||
|
||||
if matchesKey(fieldKeySelector, &field) {
|
||||
if _, exists := setOfKeys[field.Text()]; !exists {
|
||||
result[field.Name] = append(result[field.Name], &field)
|
||||
setOfKeys[field.Text()] = &field
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,7 +130,7 @@ func (m *MockMetadataStore) GetKey(ctx context.Context, fieldKeySelector *teleme
|
||||
|
||||
result := []*telemetrytypes.TelemetryFieldKey{}
|
||||
|
||||
// Find keys matching the selector
|
||||
// Find keys matching the selector from KeysMap
|
||||
for name, keys := range m.KeysMap {
|
||||
if matchesName(fieldKeySelector, name) {
|
||||
for _, key := range keys {
|
||||
@@ -119,6 +141,17 @@ func (m *MockMetadataStore) GetKey(ctx context.Context, fieldKeySelector *teleme
|
||||
}
|
||||
}
|
||||
|
||||
// Add matching StaticFields (e.g. IntrinsicFields), same as the real metadata store does
|
||||
for key, field := range m.StaticFields {
|
||||
if !matchesName(fieldKeySelector, key) {
|
||||
continue
|
||||
}
|
||||
|
||||
if matchesKey(fieldKeySelector, &field) {
|
||||
result = append(result, &field)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -61,3 +61,55 @@ def test_api_key(signoz: types.SigNoz, get_token: Callable[[str, str], str]) ->
|
||||
assert found_pat["userId"] == found_user["id"]
|
||||
assert found_pat["name"] == "admin"
|
||||
assert found_pat["role"] == "ADMIN"
|
||||
|
||||
|
||||
def test_api_key_role(signoz: types.SigNoz, get_token: Callable[[str, str], str]) -> None:
|
||||
admin_token = get_token("admin@integration.test", "password123Z$")
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/pats"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json={
|
||||
"name": "viewer",
|
||||
"role": "VIEWER",
|
||||
"expiresInDays": 1,
|
||||
},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.CREATED
|
||||
pat_response = response.json()
|
||||
assert "data" in pat_response
|
||||
assert "token" in pat_response["data"]
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/user"),
|
||||
timeout=2,
|
||||
headers={"SIGNOZ-API-KEY": f"{pat_response["data"]["token"]}"},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.FORBIDDEN
|
||||
|
||||
response = requests.post(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/pats"),
|
||||
headers={"Authorization": f"Bearer {admin_token}"},
|
||||
json={
|
||||
"name": "editor",
|
||||
"role": "EDITOR",
|
||||
"expiresInDays": 1,
|
||||
},
|
||||
timeout=2,
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.CREATED
|
||||
pat_response = response.json()
|
||||
assert "data" in pat_response
|
||||
assert "token" in pat_response["data"]
|
||||
|
||||
response = requests.get(
|
||||
signoz.self.host_configs["8080"].get("/api/v1/user"),
|
||||
timeout=2,
|
||||
headers={"SIGNOZ-API-KEY": f"{pat_response["data"]["token"]}"},
|
||||
)
|
||||
|
||||
assert response.status_code == HTTPStatus.FORBIDDEN
|
||||
|
||||
Reference in New Issue
Block a user