diff --git a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx index e63a8dbb0e..0916cb0e96 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx @@ -22,8 +22,9 @@ import { RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY, RUNNING_QUERIES_SELECTED_COLUMNS_LS_KEY, } from './columns/constants'; +import {useRunningQueriesSort} from './hooks/useRunningQueriesSort'; import i18n from './i18n'; -import {TOP_QUERIES_TABLE_SETTINGS, useRunningQueriesSort} from './utils'; +import {TOP_QUERIES_TABLE_SETTINGS} from './utils'; const b = cn('kv-top-queries'); diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx index debd299a6a..cbd3a4a271 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx @@ -26,8 +26,9 @@ import { TOP_QUERIES_SELECTED_COLUMNS_LS_KEY, } from './columns/constants'; import {DEFAULT_TIME_FILTER_VALUE, TIME_FRAME_OPTIONS} from './constants'; +import {useTopQueriesSort} from './hooks/useTopQueriesSort'; import i18n from './i18n'; -import {TOP_QUERIES_TABLE_SETTINGS, useTopQueriesSort} from './utils'; +import {TOP_QUERIES_TABLE_SETTINGS} from './utils'; const b = cn('kv-top-queries'); @@ -67,6 +68,7 @@ export const TopQueriesData = ({ REQUIRED_TOP_QUERIES_COLUMNS, ); + // Use the sort params from URL in the hook const {tableSort, handleTableSort, backendSort} = useTopQueriesSort(); const {currentData, data, isFetching, isLoading, error} = topQueriesApi.useGetTopQueriesQuery( diff --git a/src/containers/Tenant/Diagnostics/TopQueries/hooks/useRunningQueriesSort.ts b/src/containers/Tenant/Diagnostics/TopQueries/hooks/useRunningQueriesSort.ts new file mode 100644 index 0000000000..a5da686149 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TopQueries/hooks/useRunningQueriesSort.ts @@ -0,0 +1,48 @@ +import React from 'react'; + +import type {SortOrder} from '@gravity-ui/react-data-table'; +import DataTable from '@gravity-ui/react-data-table'; +import {z} from 'zod'; + +import {prepareBackendSortFieldsFromTableSort, useTableSort} from '../../../../../utils/hooks'; +import {QUERIES_COLUMNS_IDS, getRunningQueriesColumnSortField} from '../columns/constants'; + +import {useSortParam} from './useSortParam'; + +export const runningQueriesSortColumnSchema = z + .enum([ + QUERIES_COLUMNS_IDS.QueryStartAt, + QUERIES_COLUMNS_IDS.UserSID, + QUERIES_COLUMNS_IDS.ApplicationName, + ]) + .catch(QUERIES_COLUMNS_IDS.QueryStartAt); + +export const DEFAULT_RUNNING_QUERIES_SORT: SortOrder = { + columnId: QUERIES_COLUMNS_IDS.QueryStartAt, + order: DataTable.DESCENDING, +}; + +export function useRunningQueriesSort() { + const {sortParam, updateSortParam} = useSortParam({ + paramName: 'runningSort', + schema: runningQueriesSortColumnSchema, + defaultSort: DEFAULT_RUNNING_QUERIES_SORT, + }); + + const [tableSort, handleTableSort] = useTableSort({ + initialSortColumn: sortParam?.[0]?.columnId || DEFAULT_RUNNING_QUERIES_SORT.columnId, + initialSortOrder: sortParam?.[0]?.order || DEFAULT_RUNNING_QUERIES_SORT.order, + multiple: true, + onSort: updateSortParam, + }); + + return { + tableSort, + handleTableSort, + backendSort: React.useMemo( + () => + prepareBackendSortFieldsFromTableSort(tableSort, getRunningQueriesColumnSortField), + [tableSort], + ), + }; +} diff --git a/src/containers/Tenant/Diagnostics/TopQueries/hooks/useSortParam.ts b/src/containers/Tenant/Diagnostics/TopQueries/hooks/useSortParam.ts new file mode 100644 index 0000000000..04aced9e66 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TopQueries/hooks/useSortParam.ts @@ -0,0 +1,66 @@ +import React from 'react'; + +import type {SortOrder} from '@gravity-ui/react-data-table'; +import type {QueryParamConfig} from 'use-query-params'; +import {useQueryParam} from 'use-query-params'; +import type {z} from 'zod'; + +const SortOrderParam: QueryParamConfig = { + encode: (value) => { + if (value === undefined || value === null || !Array.isArray(value)) { + return undefined; + } + + return encodeURIComponent(JSON.stringify(value)); + }, + decode: (value) => { + if (typeof value !== 'string' || !value) { + return []; + } + try { + return JSON.parse(decodeURIComponent(value)); + } catch { + return []; + } + }, +}; + +export function useSortParam(options: { + paramName: string; + schema: z.ZodType; + defaultSort: SortOrder; +}) { + const {paramName, schema, defaultSort} = options; + const [urlSortParam, setUrlSortParam] = useQueryParam(paramName, SortOrderParam); + + const parsedUrlSort = React.useMemo(() => { + if (!urlSortParam || !Array.isArray(urlSortParam) || urlSortParam.length === 0) { + return [defaultSort]; + } + + const validSortParams = urlSortParam.filter((sort: SortOrder) => { + try { + schema.parse(sort.columnId); + return true; + } catch { + return false; + } + }); + + return validSortParams.length ? validSortParams : [defaultSort]; + }, [urlSortParam, schema, defaultSort]); + + const updateSortParam = React.useCallback( + (sortOrder?: SortOrder[]) => { + // Using 'replace' instead of 'replaceIn' to ensure a full URL update + // This helps prevent issues with partial URL parameter updates + setUrlSortParam(sortOrder || [], 'replaceIn'); + }, + [setUrlSortParam], + ); + + return { + sortParam: parsedUrlSort, + updateSortParam, + }; +} diff --git a/src/containers/Tenant/Diagnostics/TopQueries/hooks/useTopQueriesSort.ts b/src/containers/Tenant/Diagnostics/TopQueries/hooks/useTopQueriesSort.ts new file mode 100644 index 0000000000..220cc9358c --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TopQueries/hooks/useTopQueriesSort.ts @@ -0,0 +1,50 @@ +import React from 'react'; + +import type {SortOrder} from '@gravity-ui/react-data-table'; +import DataTable from '@gravity-ui/react-data-table'; +import {z} from 'zod'; + +import {prepareBackendSortFieldsFromTableSort, useTableSort} from '../../../../../utils/hooks'; +import {QUERIES_COLUMNS_IDS, getTopQueriesColumnSortField} from '../columns/constants'; + +import {useSortParam} from './useSortParam'; + +export const topQueriesSortColumnSchema = z + .enum([ + QUERIES_COLUMNS_IDS.CPUTime, + QUERIES_COLUMNS_IDS.Duration, + QUERIES_COLUMNS_IDS.ReadRows, + QUERIES_COLUMNS_IDS.ReadBytes, + QUERIES_COLUMNS_IDS.QueryText, + ]) + .catch(QUERIES_COLUMNS_IDS.CPUTime); + +export const DEFAULT_TOP_QUERIES_SORT: SortOrder = { + columnId: QUERIES_COLUMNS_IDS.CPUTime, + order: DataTable.DESCENDING, +}; + +export function useTopQueriesSort() { + const {sortParam, updateSortParam} = useSortParam({ + paramName: 'topSort', + schema: topQueriesSortColumnSchema, + defaultSort: DEFAULT_TOP_QUERIES_SORT, + }); + + const [tableSort, handleTableSort] = useTableSort({ + initialSortColumn: sortParam?.[0]?.columnId || DEFAULT_TOP_QUERIES_SORT.columnId, + initialSortOrder: sortParam?.[0]?.order || DEFAULT_TOP_QUERIES_SORT.order, + multiple: true, + fixedOrderType: DataTable.DESCENDING, + onSort: updateSortParam, + }); + + return { + tableSort, + handleTableSort, + backendSort: React.useMemo( + () => prepareBackendSortFieldsFromTableSort(tableSort, getTopQueriesColumnSortField), + [tableSort], + ), + }; +} diff --git a/src/containers/Tenant/Diagnostics/TopQueries/utils.ts b/src/containers/Tenant/Diagnostics/TopQueries/utils.ts index ffc2bed9e6..05b229abdd 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/utils.ts +++ b/src/containers/Tenant/Diagnostics/TopQueries/utils.ts @@ -1,55 +1,9 @@ -import React from 'react'; +import type {Settings} from '@gravity-ui/react-data-table'; -import type {Settings, SortOrder} from '@gravity-ui/react-data-table'; -import DataTable from '@gravity-ui/react-data-table'; - -import {prepareBackendSortFieldsFromTableSort, useTableSort} from '../../../../utils/hooks'; import {QUERY_TABLE_SETTINGS} from '../../utils/constants'; -import { - QUERIES_COLUMNS_IDS, - getRunningQueriesColumnSortField, - getTopQueriesColumnSortField, -} from './columns/constants'; - export const TOP_QUERIES_TABLE_SETTINGS: Settings = { ...QUERY_TABLE_SETTINGS, disableSortReset: true, externalSort: true, }; - -export function useTopQueriesSort(initialSort?: SortOrder[]) { - const [tableSort, handleTableSort] = useTableSort({ - initialSortColumn: initialSort?.[0]?.columnId || QUERIES_COLUMNS_IDS.CPUTime, - initialSortOrder: initialSort?.[0]?.order || DataTable.DESCENDING, - multiple: true, - fixedOrderType: DataTable.DESCENDING, - }); - - return { - tableSort, - handleTableSort, - backendSort: React.useMemo( - () => prepareBackendSortFieldsFromTableSort(tableSort, getTopQueriesColumnSortField), - [tableSort], - ), - }; -} - -export function useRunningQueriesSort() { - const [tableSort, handleTableSort] = useTableSort({ - initialSortColumn: QUERIES_COLUMNS_IDS.QueryStartAt, - initialSortOrder: DataTable.DESCENDING, - multiple: true, - }); - - return { - tableSort, - handleTableSort, - backendSort: React.useMemo( - () => - prepareBackendSortFieldsFromTableSort(tableSort, getRunningQueriesColumnSortField), - [tableSort], - ), - }; -}