From 250661a515a0fe1f77eae9368e8152f8f10bce8f Mon Sep 17 00:00:00 2001 From: sareyu Date: Wed, 18 Sep 2024 14:13:03 +0300 Subject: [PATCH 1/8] feat: show running queries --- .../Tenant/Diagnostics/DiagnosticsPages.ts | 2 +- .../TopQueries/RunningQueriesData.tsx | 89 ++++++++++++++++++ .../Diagnostics/TopQueries/TopQueries.scss | 2 +- .../Diagnostics/TopQueries/TopQueries.tsx | 93 ++++++------------- .../Diagnostics/TopQueries/TopQueriesData.tsx | 62 +++++++++++++ .../executeTopQueries/executeTopQueries.ts | 35 ++++++- 6 files changed, 217 insertions(+), 66 deletions(-) create mode 100644 src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx create mode 100644 src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx diff --git a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts index b60b04839..6400ec119 100644 --- a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts +++ b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts @@ -19,7 +19,7 @@ const schema = { const topQueries = { id: TENANT_DIAGNOSTICS_TABS_IDS.topQueries, - title: 'Top queries', + title: 'Queries', }; const topShards = { diff --git a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx new file mode 100644 index 000000000..403fd7642 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx @@ -0,0 +1,89 @@ +import type {Column} from '@gravity-ui/react-data-table'; + +import {ResponseError} from '../../../../components/Errors/ResponseError'; +import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; +import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; +import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery'; +import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries'; +import type {KeyValueRow} from '../../../../types/api/query'; +import {cn} from '../../../../utils/cn'; +import {formatDateTime} from '../../../../utils/dataFormatters/dataFormatters'; +import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; +import {MAX_QUERY_HEIGHT, QUERY_TABLE_SETTINGS} from '../../utils/constants'; + +import i18n from './i18n'; + +const b = cn('kv-top-queries'); + +interface Props { + database: string; +} + +const RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY = 'runningQueriesColumnsWidth'; + +const columns: Column[] = [ + { + name: 'UserSID', + header: 'User', + render: ({row}) => row.UserSID || '-', + sortable: false, + }, + { + name: 'QueryStartAt', + header: 'Start time', + render: ({row}) => formatDateTime(new Date(row.QueryStartAt as string).getTime()), + sortable: false, + }, + { + name: 'Query', + headerTitle: 'Query text', + render: ({row}) => ( +
+ +
+ ), + width: 500, + sortable: false, + }, + { + name: 'ApplicationName', + header: 'Application', + render: ({row}) => row.ApplicationName || '-', + sortable: false, + }, +]; + +export const RunningQueriesData = ({database}: Props) => { + const [autoRefreshInterval] = useAutoRefreshInterval(); + const filters = useTypedSelector((state) => state.executeTopQueries); + const { + currentData: data, + isFetching, + error, + } = topQueriesApi.useGetRunningQueriesQuery( + { + database, + filters, + }, + {pollingInterval: autoRefreshInterval}, + ); + + console.log(data); + console.log(error); + + return ( + + {error ? ( + + ) : ( + + )} + + ); +}; diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss index 99cb59533..7b682a942 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss @@ -12,7 +12,7 @@ @include search(); } - &__row { + tr { cursor: pointer; } diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx index 807b18c3a..9cc9d23b4 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx @@ -1,19 +1,17 @@ import React from 'react'; +import type {RadioButtonOption} from '@gravity-ui/uikit'; +import {RadioButton} from '@gravity-ui/uikit'; import {useHistory, useLocation} from 'react-router-dom'; +import {StringParam, useQueryParam} from 'use-query-params'; import type {DateRangeValues} from '../../../../components/DateRange'; import {DateRange} from '../../../../components/DateRange'; -import {ResponseError} from '../../../../components/Errors/ResponseError'; -import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; import {Search} from '../../../../components/Search'; import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; import {parseQuery} from '../../../../routes'; import {changeUserInput} from '../../../../store/reducers/executeQuery'; -import { - setTopQueriesFilters, - topQueriesApi, -} from '../../../../store/reducers/executeTopQueries/executeTopQueries'; +import {setTopQueriesFilters} from '../../../../store/reducers/executeTopQueries/executeTopQueries'; import { TENANT_PAGE, TENANT_PAGES_IDS, @@ -21,14 +19,11 @@ import { } from '../../../../store/reducers/tenant/constants'; import type {EPathType} from '../../../../types/api/schema'; import {cn} from '../../../../utils/cn'; -import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics'; -import {useAutoRefreshInterval, useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; -import {parseQueryErrorToString} from '../../../../utils/query'; +import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; import {TenantTabsGroups, getTenantPath} from '../../TenantPages'; -import {QUERY_TABLE_SETTINGS} from '../../utils/constants'; -import {isColumnEntityType} from '../../utils/schema'; -import {TOP_QUERIES_COLUMNS, TOP_QUERIES_COLUMNS_WIDTH_LS_KEY} from './getTopQueriesColumns'; +import {RunningQueriesData} from './RunningQueriesData'; +import {TopQueriesData} from './TopQueriesData'; import i18n from './i18n'; import './TopQueries.scss'; @@ -44,25 +39,11 @@ export const TopQueries = ({tenantName, type}: TopQueriesProps) => { const dispatch = useTypedDispatch(); const location = useLocation(); const history = useHistory(); + const [queryMode = 'top', setQueryMode] = useQueryParam('queryMode', StringParam); - const [autoRefreshInterval] = useAutoRefreshInterval(); + const isTopQueries = queryMode === 'top'; const filters = useTypedSelector((state) => state.executeTopQueries); - const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery( - { - database: tenantName, - filters, - }, - {pollingInterval: autoRefreshInterval}, - ); - const loading = isFetching && currentData === undefined; - const {result: data} = currentData || {}; - - const rawColumns = TOP_QUERIES_COLUMNS; - const columns = rawColumns.map((column) => ({ - ...column, - sortable: isSortableTopQueriesProperty(column.name), - })); const handleRowClick = React.useCallback( (row: any) => { @@ -91,48 +72,34 @@ export const TopQueries = ({tenantName, type}: TopQueriesProps) => { dispatch(setTopQueriesFilters(value)); }; - const renderContent = () => { - if (error && !data) { - return null; - } - - if (!data || isColumnEntityType(type)) { - return i18n('no-data'); - } - - return ( - b('row')} - /> - ); - }; + const options: RadioButtonOption[] = [ + {value: 'top', content: 'Top'}, + {value: 'running', content: 'Running'}, + ]; - const renderControls = () => { - return ( - + return ( + + + - - - ); - }; - - return ( - - {renderControls()} - {error ? : null} - - {renderContent()} - + {queryMode === 'top' ? ( + + ) : null} + + {isTopQueries ? ( + + ) : ( + + )} ); }; diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx new file mode 100644 index 000000000..23e07f0aa --- /dev/null +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx @@ -0,0 +1,62 @@ +import {ResponseError} from '../../../../components/Errors/ResponseError'; +import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; +import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; +import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries'; +import type {EPathType} from '../../../../types/api/schema'; +import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics'; +import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; +import {QUERY_TABLE_SETTINGS} from '../../utils/constants'; +import {isColumnEntityType} from '../../utils/schema'; + +import {TOP_QUERIES_COLUMNS, TOP_QUERIES_COLUMNS_WIDTH_LS_KEY} from './getTopQueriesColumns'; +import i18n from './i18n'; + +interface Props { + database: string; + onRowClick: (row: any) => void; + type?: EPathType; +} + +export const TopQueriesData = ({database, onRowClick, type}: Props) => { + const [autoRefreshInterval] = useAutoRefreshInterval(); + const filters = useTypedSelector((state) => state.executeTopQueries); + const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery( + { + database, + filters, + }, + {pollingInterval: autoRefreshInterval}, + ); + const loading = isFetching && currentData === undefined; + const {result: data} = currentData || {}; + + const rawColumns = TOP_QUERIES_COLUMNS; + const columns = rawColumns.map((column) => ({ + ...column, + sortable: isSortableTopQueriesProperty(column.name), + })); + + if (error && !data) { + return ( + + + + ); + } + + if (!data || isColumnEntityType(type)) { + return {i18n('no-data')}; + } + + return ( + + + + ); +}; diff --git a/src/store/reducers/executeTopQueries/executeTopQueries.ts b/src/store/reducers/executeTopQueries/executeTopQueries.ts index 6b096d421..95b3308f9 100644 --- a/src/store/reducers/executeTopQueries/executeTopQueries.ts +++ b/src/store/reducers/executeTopQueries/executeTopQueries.ts @@ -66,10 +66,11 @@ export const topQueriesApi = api.injectEndpoints({ ); if (isQueryErrorResponse(response)) { - return {error: response}; + throw response; } const data = parseQueryAPIExecuteResponse(response); + console.log(data); return {data}; } catch (error) { return {error}; @@ -88,6 +89,38 @@ export const topQueriesApi = api.injectEndpoints({ return false; }, }), + getRunningQueries: build.query({ + queryFn: async ( + {database, filters}: {database: string; filters?: TopQueriesFilters}, + {signal}, + ) => { + try { + const filterConditions = filters?.text + ? `QueryText ILIKE '%${filters.text}%'` + : ''; + + const response = await window.api.sendQuery( + { + query: `SELECT * from \`.sys/query_sessions\` WHERE ${filterConditions || 'true'} limit 10`, + database, + }, + {signal, withRetries: true}, + ); + + if (isQueryErrorResponse(response)) { + throw response; + } + + console.log(response); + return {data: response}; + } catch (error) { + return {error}; + } + }, + forceRefetch() { + return true; + }, + }), }), overrideExisting: 'throw', }); From 9ba6997480b1ebc10eb0d81358d7da5bacaf9e68 Mon Sep 17 00:00:00 2001 From: sareyu Date: Fri, 20 Sep 2024 11:51:13 +0300 Subject: [PATCH 2/8] fix: fixup --- .../TopQueries/RunningQueriesData.tsx | 23 +++++++--------- .../Diagnostics/TopQueries/TopQueries.scss | 2 +- .../Diagnostics/TopQueries/TopQueries.tsx | 26 ++++++++++--------- .../Diagnostics/TopQueries/TopQueriesData.tsx | 11 +++++++- .../Diagnostics/TopQueries/i18n/en.json | 8 +++++- .../Diagnostics/TopQueries/i18n/ru.json | 8 +++++- .../executeTopQueries/executeTopQueries.ts | 12 ++++----- 7 files changed, 54 insertions(+), 36 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx index 403fd7642..b7f8fd6c2 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx @@ -23,20 +23,20 @@ const RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY = 'runningQueriesColumnsWidth'; const columns: Column[] = [ { - name: 'UserSID', - header: 'User', + name: 'user', + header: i18n('col_user'), render: ({row}) => row.UserSID || '-', sortable: false, }, { - name: 'QueryStartAt', - header: 'Start time', + name: 'startTime', + header: i18n('col_start-time'), render: ({row}) => formatDateTime(new Date(row.QueryStartAt as string).getTime()), sortable: false, }, { - name: 'Query', - headerTitle: 'Query text', + name: 'queryText', + header: i18n('col_query-text'), render: ({row}) => (
@@ -46,8 +46,8 @@ const columns: Column[] = [ sortable: false, }, { - name: 'ApplicationName', - header: 'Application', + name: 'app', + header: i18n('col_app'), render: ({row}) => row.ApplicationName || '-', sortable: false, }, @@ -68,19 +68,16 @@ export const RunningQueriesData = ({database}: Props) => { {pollingInterval: autoRefreshInterval}, ); - console.log(data); - console.log(error); - return ( {error ? ( - + ) : ( )} diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss index 7b682a942..99cb59533 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.scss @@ -12,7 +12,7 @@ @include search(); } - tr { + &__row { cursor: pointer; } diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx index 9cc9d23b4..7d56a82c2 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx @@ -30,6 +30,11 @@ import './TopQueries.scss'; const b = cn('kv-top-queries'); +const QUERY_MODE_OPTIONS: RadioButtonOption[] = [ + {value: 'top', content: i18n('mode_top')}, + {value: 'running', content: i18n('mode_running')}, +]; + interface TopQueriesProps { tenantName: string; type?: EPathType; @@ -45,10 +50,8 @@ export const TopQueries = ({tenantName, type}: TopQueriesProps) => { const filters = useTypedSelector((state) => state.executeTopQueries); - const handleRowClick = React.useCallback( - (row: any) => { - const {QueryText: input} = row; - + const onRowClick = React.useCallback( + (input: string) => { dispatch(changeUserInput({input})); const queryParams = parseQuery(location); @@ -72,22 +75,21 @@ export const TopQueries = ({tenantName, type}: TopQueriesProps) => { dispatch(setTopQueriesFilters(value)); }; - const options: RadioButtonOption[] = [ - {value: 'top', content: 'Top'}, - {value: 'running', content: 'Running'}, - ]; - return ( - + - {queryMode === 'top' ? ( + {isTopQueries ? ( { ) : null} {isTopQueries ? ( - + ) : ( )} diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx index 23e07f0aa..1e2e16b3b 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx @@ -2,7 +2,9 @@ import {ResponseError} from '../../../../components/Errors/ResponseError'; import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries'; +import type {KeyValueRow} from '../../../../types/api/query'; import type {EPathType} from '../../../../types/api/schema'; +import {cn} from '../../../../utils/cn'; import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics'; import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; import {QUERY_TABLE_SETTINGS} from '../../utils/constants'; @@ -11,6 +13,8 @@ import {isColumnEntityType} from '../../utils/schema'; import {TOP_QUERIES_COLUMNS, TOP_QUERIES_COLUMNS_WIDTH_LS_KEY} from './getTopQueriesColumns'; import i18n from './i18n'; +const b = cn('kv-top-queries'); + interface Props { database: string; onRowClick: (row: any) => void; @@ -36,6 +40,10 @@ export const TopQueriesData = ({database, onRowClick, type}: Props) => { sortable: isSortableTopQueriesProperty(column.name), })); + const handleRowClick = (row: KeyValueRow) => { + return onRowClick(row.QueryText as string); + }; + if (error && !data) { return ( @@ -55,7 +63,8 @@ export const TopQueriesData = ({database, onRowClick, type}: Props) => { columns={columns} data={data} settings={QUERY_TABLE_SETTINGS} - onRowClick={onRowClick} + onRowClick={handleRowClick} + rowClassName={() => b('row')} /> ); diff --git a/src/containers/Tenant/Diagnostics/TopQueries/i18n/en.json b/src/containers/Tenant/Diagnostics/TopQueries/i18n/en.json index 7674260b1..43ef7d1c0 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/i18n/en.json +++ b/src/containers/Tenant/Diagnostics/TopQueries/i18n/en.json @@ -1,4 +1,10 @@ { "no-data": "No data", - "filter.text.placeholder": "Search by query text..." + "filter.text.placeholder": "Search by query text...", + "mode_top": "Top", + "mode_running": "Running", + "col_user": "User", + "col_start-time": "Start time", + "col_query-text": "Query text", + "col_app": "Application" } diff --git a/src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json b/src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json index 90aeb4eda..d6ba474d7 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json +++ b/src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json @@ -1,4 +1,10 @@ { "no-data": "Нет данных", - "filter.text.placeholder": "Искать по тексту запроса..." + "filter.text.placeholder": "Искать по тексту запроса...", + "mode_top": "Top", + "mode_running": "Running", + "col_user": "User", + "col_start-time": "Start time", + "col_query-text": "Query text", + "col_app": "Application" } diff --git a/src/store/reducers/executeTopQueries/executeTopQueries.ts b/src/store/reducers/executeTopQueries/executeTopQueries.ts index 95b3308f9..86cea497b 100644 --- a/src/store/reducers/executeTopQueries/executeTopQueries.ts +++ b/src/store/reducers/executeTopQueries/executeTopQueries.ts @@ -70,7 +70,6 @@ export const topQueriesApi = api.injectEndpoints({ } const data = parseQueryAPIExecuteResponse(response); - console.log(data); return {data}; } catch (error) { return {error}; @@ -95,14 +94,14 @@ export const topQueriesApi = api.injectEndpoints({ {signal}, ) => { try { - const filterConditions = filters?.text - ? `QueryText ILIKE '%${filters.text}%'` - : ''; + const filterConditions = filters?.text ? `Query ILIKE '%${filters.text}%'` : ''; + const queryText = `SELECT * from \`.sys/query_sessions\` WHERE ${filterConditions || 'true'} limit 10`; const response = await window.api.sendQuery( { - query: `SELECT * from \`.sys/query_sessions\` WHERE ${filterConditions || 'true'} limit 10`, + query: queryText, database, + action: 'execute-query', }, {signal, withRetries: true}, ); @@ -111,8 +110,7 @@ export const topQueriesApi = api.injectEndpoints({ throw response; } - console.log(response); - return {data: response}; + return {data: response?.result?.filter((item) => item.Query !== queryText)}; } catch (error) { return {error}; } From b2f786e199d5dbb5a22d74cd648613c1bb20bc75 Mon Sep 17 00:00:00 2001 From: sareyu Date: Mon, 23 Sep 2024 14:22:42 +0300 Subject: [PATCH 3/8] fix: add limit and sorting --- .../TopQueries/RunningQueriesData.tsx | 29 ++++++++++++------- .../executeTopQueries/executeTopQueries.ts | 2 +- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx index b7f8fd6c2..65eb85a33 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx @@ -1,11 +1,12 @@ import type {Column} from '@gravity-ui/react-data-table'; +import DataTable from '@gravity-ui/react-data-table'; import {ResponseError} from '../../../../components/Errors/ResponseError'; import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery'; import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries'; -import type {KeyValueRow} from '../../../../types/api/query'; +import type {CellValue, KeyValueRow} from '../../../../types/api/query'; import {cn} from '../../../../utils/cn'; import {formatDateTime} from '../../../../utils/dataFormatters/dataFormatters'; import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; @@ -21,21 +22,29 @@ interface Props { const RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY = 'runningQueriesColumnsWidth'; +const parseDate = (date: CellValue) => (date ? new Date(date.toString()).getTime() : ''); + const columns: Column[] = [ { - name: 'user', + name: 'UserSID', header: i18n('col_user'), - render: ({row}) => row.UserSID || '-', - sortable: false, + render: ({row}) =>
{row.UserSID || '–'}
, + sortAccessor: (row) => String(row.UserSID), + sortable: true, }, { - name: 'startTime', + name: 'QueryStartAt', header: i18n('col_start-time'), - render: ({row}) => formatDateTime(new Date(row.QueryStartAt as string).getTime()), - sortable: false, + render: ({row}) => formatDateTime(parseDate(row.QueryStartAt)), + sortable: true, + resizeable: false, + defaultOrder: DataTable.DESCENDING, + sortAccessor: (row) => { + return parseDate(row.QueryStartAt); + }, }, { - name: 'queryText', + name: 'Query', header: i18n('col_query-text'), render: ({row}) => (
@@ -46,10 +55,10 @@ const columns: Column[] = [ sortable: false, }, { - name: 'app', + name: 'ApplicationName', header: i18n('col_app'), render: ({row}) => row.ApplicationName || '-', - sortable: false, + sortable: true, }, ]; diff --git a/src/store/reducers/executeTopQueries/executeTopQueries.ts b/src/store/reducers/executeTopQueries/executeTopQueries.ts index 86cea497b..c475d7717 100644 --- a/src/store/reducers/executeTopQueries/executeTopQueries.ts +++ b/src/store/reducers/executeTopQueries/executeTopQueries.ts @@ -95,7 +95,7 @@ export const topQueriesApi = api.injectEndpoints({ ) => { try { const filterConditions = filters?.text ? `Query ILIKE '%${filters.text}%'` : ''; - const queryText = `SELECT * from \`.sys/query_sessions\` WHERE ${filterConditions || 'true'} limit 10`; + const queryText = `SELECT * from \`.sys/query_sessions\` WHERE ${filterConditions || 'true'} ORDER BY SessionStartAt limit 100`; const response = await window.api.sendQuery( { From 12b327393499c4ec7db3ec6e262dc9c6514f4ee8 Mon Sep 17 00:00:00 2001 From: sareyu Date: Mon, 23 Sep 2024 15:15:24 +0300 Subject: [PATCH 4/8] fix: fixes --- .../Diagnostics/TopQueries/RunningQueriesData.tsx | 14 ++++---------- .../executeTopQueries/executeTopQueries.ts | 2 ++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx index 65eb85a33..70103c19a 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx @@ -6,7 +6,7 @@ import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/Re import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery'; import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries'; -import type {CellValue, KeyValueRow} from '../../../../types/api/query'; +import type {KeyValueRow} from '../../../../types/api/query'; import {cn} from '../../../../utils/cn'; import {formatDateTime} from '../../../../utils/dataFormatters/dataFormatters'; import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; @@ -22,26 +22,20 @@ interface Props { const RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY = 'runningQueriesColumnsWidth'; -const parseDate = (date: CellValue) => (date ? new Date(date.toString()).getTime() : ''); - const columns: Column[] = [ { name: 'UserSID', header: i18n('col_user'), render: ({row}) =>
{row.UserSID || '–'}
, - sortAccessor: (row) => String(row.UserSID), sortable: true, }, { name: 'QueryStartAt', header: i18n('col_start-time'), - render: ({row}) => formatDateTime(parseDate(row.QueryStartAt)), + render: ({row}) => formatDateTime(new Date(row.QueryStartAt as string).getTime()), sortable: true, resizeable: false, defaultOrder: DataTable.DESCENDING, - sortAccessor: (row) => { - return parseDate(row.QueryStartAt); - }, }, { name: 'Query', @@ -57,7 +51,7 @@ const columns: Column[] = [ { name: 'ApplicationName', header: i18n('col_app'), - render: ({row}) => row.ApplicationName || '-', + render: ({row}) =>
{row.ApplicationName || '–'}
, sortable: true, }, ]; @@ -78,7 +72,7 @@ export const RunningQueriesData = ({database}: Props) => { ); return ( - + {error ? ( ) : ( diff --git a/src/store/reducers/executeTopQueries/executeTopQueries.ts b/src/store/reducers/executeTopQueries/executeTopQueries.ts index c475d7717..786f96cfe 100644 --- a/src/store/reducers/executeTopQueries/executeTopQueries.ts +++ b/src/store/reducers/executeTopQueries/executeTopQueries.ts @@ -87,6 +87,7 @@ export const topQueriesApi = api.injectEndpoints({ return false; }, + providesTags: ['All'], }), getRunningQueries: build.query({ queryFn: async ( @@ -118,6 +119,7 @@ export const topQueriesApi = api.injectEndpoints({ forceRefetch() { return true; }, + providesTags: ['All'], }), }), overrideExisting: 'throw', From 7b7848cf693112292b14492337e99ab0c63cfa1c Mon Sep 17 00:00:00 2001 From: sareyu Date: Tue, 24 Sep 2024 14:11:26 +0300 Subject: [PATCH 5/8] fix: review fixes --- .../Tenant/Diagnostics/Diagnostics.tsx | 2 +- .../TopQueries/RunningQueriesData.tsx | 65 ++++--------------- .../Diagnostics/TopQueries/TopQueries.tsx | 34 +++++++--- .../Diagnostics/TopQueries/TopQueriesData.tsx | 47 ++++++-------- .../TopQueries/getTopQueriesColumns.tsx | 30 +++++++++ .../Diagnostics/TopQueries/i18n/index.ts | 3 +- .../Diagnostics/TopQueries/i18n/ru.json | 10 --- .../executeTopQueries/executeTopQueries.ts | 6 +- 8 files changed, 93 insertions(+), 104 deletions(-) delete mode 100644 src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx index 645e88f7e..592e7948c 100644 --- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx +++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx @@ -93,7 +93,7 @@ function Diagnostics(props: DiagnosticsProps) { return ; } case TENANT_DIAGNOSTICS_TABS_IDS.topQueries: { - return ; + return ; } case TENANT_DIAGNOSTICS_TABS_IDS.topShards: { return ; diff --git a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx index 70103c19a..9a40f8a58 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/RunningQueriesData.tsx @@ -1,61 +1,23 @@ -import type {Column} from '@gravity-ui/react-data-table'; -import DataTable from '@gravity-ui/react-data-table'; +import React from 'react'; import {ResponseError} from '../../../../components/Errors/ResponseError'; import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; -import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery'; import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries'; -import type {KeyValueRow} from '../../../../types/api/query'; -import {cn} from '../../../../utils/cn'; -import {formatDateTime} from '../../../../utils/dataFormatters/dataFormatters'; import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; -import {MAX_QUERY_HEIGHT, QUERY_TABLE_SETTINGS} from '../../utils/constants'; +import {parseQueryErrorToString} from '../../../../utils/query'; +import {QUERY_TABLE_SETTINGS} from '../../utils/constants'; +import { + RUNNING_QUERIES_COLUMNS, + RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY, +} from './getTopQueriesColumns'; import i18n from './i18n'; -const b = cn('kv-top-queries'); - interface Props { database: string; } -const RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY = 'runningQueriesColumnsWidth'; - -const columns: Column[] = [ - { - name: 'UserSID', - header: i18n('col_user'), - render: ({row}) =>
{row.UserSID || '–'}
, - sortable: true, - }, - { - name: 'QueryStartAt', - header: i18n('col_start-time'), - render: ({row}) => formatDateTime(new Date(row.QueryStartAt as string).getTime()), - sortable: true, - resizeable: false, - defaultOrder: DataTable.DESCENDING, - }, - { - name: 'Query', - header: i18n('col_query-text'), - render: ({row}) => ( -
- -
- ), - width: 500, - sortable: false, - }, - { - name: 'ApplicationName', - header: i18n('col_app'), - render: ({row}) =>
{row.ApplicationName || '–'}
, - sortable: true, - }, -]; - export const RunningQueriesData = ({database}: Props) => { const [autoRefreshInterval] = useAutoRefreshInterval(); const filters = useTypedSelector((state) => state.executeTopQueries); @@ -72,18 +34,17 @@ export const RunningQueriesData = ({database}: Props) => { ); return ( - - {error ? ( - - ) : ( + + {error ? : null} + - )} - + + ); }; diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx index 7d56a82c2..947762b8b 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx @@ -17,7 +17,6 @@ import { TENANT_PAGES_IDS, TENANT_QUERY_TABS_ID, } from '../../../../store/reducers/tenant/constants'; -import type {EPathType} from '../../../../types/api/schema'; import {cn} from '../../../../utils/cn'; import {useTypedDispatch, useTypedSelector} from '../../../../utils/hooks'; import {TenantTabsGroups, getTenantPath} from '../../TenantPages'; @@ -30,21 +29,40 @@ import './TopQueries.scss'; const b = cn('kv-top-queries'); -const QUERY_MODE_OPTIONS: RadioButtonOption[] = [ - {value: 'top', content: i18n('mode_top')}, - {value: 'running', content: i18n('mode_running')}, +enum QueryModeIds { + 'top', + 'running', +} + +type QueryModeIdsType = keyof typeof QueryModeIds; + +const QUERY_MODE_OPTIONS: RadioButtonOption[] = [ + { + value: 'top', + get content() { + return i18n('mode_top'); + }, + }, + { + value: 'running', + get content() { + return i18n('mode_running'); + }, + }, ]; interface TopQueriesProps { tenantName: string; - type?: EPathType; } -export const TopQueries = ({tenantName, type}: TopQueriesProps) => { +export const TopQueries = ({tenantName}: TopQueriesProps) => { const dispatch = useTypedDispatch(); const location = useLocation(); const history = useHistory(); - const [queryMode = 'top', setQueryMode] = useQueryParam('queryMode', StringParam); + const [_queryMode = 'top', setQueryMode] = useQueryParam('queryMode', StringParam); + + const queryMode = + _queryMode && Object.keys(QueryModeIds).includes(_queryMode) ? _queryMode : 'top'; const isTopQueries = queryMode === 'top'; @@ -98,7 +116,7 @@ export const TopQueries = ({tenantName, type}: TopQueriesProps) => { ) : null} {isTopQueries ? ( - + ) : ( )} diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx index 1e2e16b3b..da3aa82a8 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueriesData.tsx @@ -1,14 +1,15 @@ +import React from 'react'; + import {ResponseError} from '../../../../components/Errors/ResponseError'; import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; import {topQueriesApi} from '../../../../store/reducers/executeTopQueries/executeTopQueries'; import type {KeyValueRow} from '../../../../types/api/query'; -import type {EPathType} from '../../../../types/api/schema'; import {cn} from '../../../../utils/cn'; import {isSortableTopQueriesProperty} from '../../../../utils/diagnostics'; import {useAutoRefreshInterval, useTypedSelector} from '../../../../utils/hooks'; +import {parseQueryErrorToString} from '../../../../utils/query'; import {QUERY_TABLE_SETTINGS} from '../../utils/constants'; -import {isColumnEntityType} from '../../utils/schema'; import {TOP_QUERIES_COLUMNS, TOP_QUERIES_COLUMNS_WIDTH_LS_KEY} from './getTopQueriesColumns'; import i18n from './i18n'; @@ -17,11 +18,10 @@ const b = cn('kv-top-queries'); interface Props { database: string; - onRowClick: (row: any) => void; - type?: EPathType; + onRowClick: (query: string) => void; } -export const TopQueriesData = ({database, onRowClick, type}: Props) => { +export const TopQueriesData = ({database, onRowClick}: Props) => { const [autoRefreshInterval] = useAutoRefreshInterval(); const filters = useTypedSelector((state) => state.executeTopQueries); const {currentData, isFetching, error} = topQueriesApi.useGetTopQueriesQuery( @@ -31,7 +31,6 @@ export const TopQueriesData = ({database, onRowClick, type}: Props) => { }, {pollingInterval: autoRefreshInterval}, ); - const loading = isFetching && currentData === undefined; const {result: data} = currentData || {}; const rawColumns = TOP_QUERIES_COLUMNS; @@ -44,28 +43,20 @@ export const TopQueriesData = ({database, onRowClick, type}: Props) => { return onRowClick(row.QueryText as string); }; - if (error && !data) { - return ( - - - - ); - } - - if (!data || isColumnEntityType(type)) { - return {i18n('no-data')}; - } - return ( - - b('row')} - /> - + + {error ? : null} + + b('row')} + /> + + ); }; diff --git a/src/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx b/src/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx index 6cbbffa10..93a7c9a9d 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/getTopQueriesColumns.tsx @@ -13,11 +13,14 @@ import {generateHash} from '../../../../utils/generateHash'; import {formatToMs, parseUsToMs} from '../../../../utils/timeParsers'; import {MAX_QUERY_HEIGHT} from '../../utils/constants'; +import i18n from './i18n'; + import './TopQueries.scss'; const b = cn('kv-top-queries'); export const TOP_QUERIES_COLUMNS_WIDTH_LS_KEY = 'topQueriesColumnsWidth'; +export const RUNNING_QUERIES_COLUMNS_WIDTH_LS_KEY = 'runningQueriesColumnsWidth'; const cpuTimeUsColumn: Column = { name: TOP_QUERIES_COLUMNS_IDS.CPUTimeUs, @@ -94,6 +97,26 @@ const durationColumn: Column = { width: 150, }; +const queryStartColumn: Column = { + name: 'QueryStartAt', + get header() { + return i18n('col_start-time'); + }, + render: ({row}) => formatDateTime(new Date(row.QueryStartAt as string).getTime()), + sortable: true, + resizeable: false, + defaultOrder: DataTable.DESCENDING, +}; + +const applicationColumn: Column = { + name: 'ApplicationName', + get header() { + return i18n('col_app'); + }, + render: ({row}) =>
{row.ApplicationName || '–'}
, + sortable: true, +}; + export const TOP_QUERIES_COLUMNS = [ cpuTimeUsColumn, queryTextColumn, @@ -109,3 +132,10 @@ export const TENANT_OVERVIEW_TOP_QUERUES_COLUMNS = [ oneLineQueryTextColumn, cpuTimeUsColumn, ]; + +export const RUNNING_QUERIES_COLUMNS = [ + userSIDColumn, + queryStartColumn, + queryTextColumn, + applicationColumn, +]; diff --git a/src/containers/Tenant/Diagnostics/TopQueries/i18n/index.ts b/src/containers/Tenant/Diagnostics/TopQueries/i18n/index.ts index d4b0035c7..891dbe947 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/i18n/index.ts +++ b/src/containers/Tenant/Diagnostics/TopQueries/i18n/index.ts @@ -1,8 +1,7 @@ import {registerKeysets} from '../../../../../utils/i18n'; import en from './en.json'; -import ru from './ru.json'; const COMPONENT = 'ydb-diagnostics-top-queries'; -export default registerKeysets(COMPONENT, {ru, en}); +export default registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json b/src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json deleted file mode 100644 index d6ba474d7..000000000 --- a/src/containers/Tenant/Diagnostics/TopQueries/i18n/ru.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "no-data": "Нет данных", - "filter.text.placeholder": "Искать по тексту запроса...", - "mode_top": "Top", - "mode_running": "Running", - "col_user": "User", - "col_start-time": "Start time", - "col_query-text": "Query text", - "col_app": "Application" -} diff --git a/src/store/reducers/executeTopQueries/executeTopQueries.ts b/src/store/reducers/executeTopQueries/executeTopQueries.ts index 786f96cfe..5dceeb70d 100644 --- a/src/store/reducers/executeTopQueries/executeTopQueries.ts +++ b/src/store/reducers/executeTopQueries/executeTopQueries.ts @@ -96,13 +96,13 @@ export const topQueriesApi = api.injectEndpoints({ ) => { try { const filterConditions = filters?.text ? `Query ILIKE '%${filters.text}%'` : ''; - const queryText = `SELECT * from \`.sys/query_sessions\` WHERE ${filterConditions || 'true'} ORDER BY SessionStartAt limit 100`; + const queryText = `SELECT UserSID, QueryStartAt, Query as QueryText, ApplicationName from \`.sys/query_sessions\` WHERE ${filterConditions || 'true'} ORDER BY SessionStartAt limit 100`; const response = await window.api.sendQuery( { query: queryText, database, - action: 'execute-query', + action: 'execute-scan', }, {signal, withRetries: true}, ); @@ -111,7 +111,7 @@ export const topQueriesApi = api.injectEndpoints({ throw response; } - return {data: response?.result?.filter((item) => item.Query !== queryText)}; + return {data: response?.result?.filter((item) => item.QueryText !== queryText)}; } catch (error) { return {error}; } From 5a4cda1257906cc3c945ed766718e64a86823612 Mon Sep 17 00:00:00 2001 From: sareyu Date: Tue, 24 Sep 2024 15:36:16 +0300 Subject: [PATCH 6/8] fix: use zod --- .../Diagnostics/TopQueries/TopQueries.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx index 947762b8b..08274835c 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx @@ -4,6 +4,7 @@ import type {RadioButtonOption} from '@gravity-ui/uikit'; import {RadioButton} from '@gravity-ui/uikit'; import {useHistory, useLocation} from 'react-router-dom'; import {StringParam, useQueryParam} from 'use-query-params'; +import {z} from 'zod'; import type {DateRangeValues} from '../../../../components/DateRange'; import {DateRange} from '../../../../components/DateRange'; @@ -29,28 +30,28 @@ import './TopQueries.scss'; const b = cn('kv-top-queries'); -enum QueryModeIds { - 'top', - 'running', -} - -type QueryModeIdsType = keyof typeof QueryModeIds; +const QueryModeIds = { + top: 'top', + running: 'running', +}; -const QUERY_MODE_OPTIONS: RadioButtonOption[] = [ +const QUERY_MODE_OPTIONS: RadioButtonOption[] = [ { - value: 'top', + value: QueryModeIds.top, get content() { return i18n('mode_top'); }, }, { - value: 'running', + value: QueryModeIds.running, get content() { return i18n('mode_running'); }, }, ]; +const queryModeSchema = z.nativeEnum(QueryModeIds).catch(QueryModeIds.top); + interface TopQueriesProps { tenantName: string; } @@ -61,8 +62,7 @@ export const TopQueries = ({tenantName}: TopQueriesProps) => { const history = useHistory(); const [_queryMode = 'top', setQueryMode] = useQueryParam('queryMode', StringParam); - const queryMode = - _queryMode && Object.keys(QueryModeIds).includes(_queryMode) ? _queryMode : 'top'; + const queryMode = queryModeSchema.parse(_queryMode); const isTopQueries = queryMode === 'top'; From 1721d5bd8637b4558c7173c7e4b928e582e89d19 Mon Sep 17 00:00:00 2001 From: sareyu Date: Tue, 24 Sep 2024 15:37:54 +0300 Subject: [PATCH 7/8] fix: use zod --- src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx index 08274835c..a824783b8 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx @@ -33,7 +33,7 @@ const b = cn('kv-top-queries'); const QueryModeIds = { top: 'top', running: 'running', -}; +} as const; const QUERY_MODE_OPTIONS: RadioButtonOption[] = [ { From 496e0919495aaa056ea8a9f40d490f024ebd23f9 Mon Sep 17 00:00:00 2001 From: sareyu Date: Tue, 24 Sep 2024 15:39:39 +0300 Subject: [PATCH 8/8] fix: use zod --- src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx index a824783b8..8cbdd635d 100644 --- a/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx +++ b/src/containers/Tenant/Diagnostics/TopQueries/TopQueries.tsx @@ -60,11 +60,11 @@ export const TopQueries = ({tenantName}: TopQueriesProps) => { const dispatch = useTypedDispatch(); const location = useLocation(); const history = useHistory(); - const [_queryMode = 'top', setQueryMode] = useQueryParam('queryMode', StringParam); + const [_queryMode = QueryModeIds.top, setQueryMode] = useQueryParam('queryMode', StringParam); const queryMode = queryModeSchema.parse(_queryMode); - const isTopQueries = queryMode === 'top'; + const isTopQueries = queryMode === QueryModeIds.top; const filters = useTypedSelector((state) => state.executeTopQueries);