From 8a90a0bff21ef971b577b5d8ab8f6a03246fb0b3 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Tue, 18 Nov 2025 22:08:04 +0300 Subject: [PATCH 1/5] refactor(QueriesHistory): move queries manipulations to hook --- .../Query/QueriesHistory/QueriesHistory.tsx | 12 +- .../Tenant/Query/QueryEditor/QueryEditor.tsx | 17 ++- .../Tenant/Query/QueryEditor/YqlEditor.tsx | 15 +-- .../Tenant/Query/QueryEditor/helpers.ts | 6 +- .../query/__test__/tabPersistence.test.tsx | 4 - src/store/reducers/query/query.ts | 126 ++---------------- src/store/reducers/query/types.ts | 11 +- src/store/reducers/query/useQueriesHistory.ts | 123 +++++++++++++++++ 8 files changed, 165 insertions(+), 149 deletions(-) create mode 100644 src/store/reducers/query/useQueriesHistory.ts diff --git a/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx b/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx index 03dd2a22a1..76393b40d0 100644 --- a/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +++ b/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx @@ -1,3 +1,5 @@ +import React from 'react'; + import type {Column} from '@gravity-ui/react-data-table'; import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; @@ -5,12 +7,12 @@ import {Search} from '../../../../components/Search'; import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; import {TruncatedQuery} from '../../../../components/TruncatedQuery/TruncatedQuery'; import { - selectQueriesHistory, selectQueriesHistoryFilter, setIsDirty, setQueryHistoryFilter, } from '../../../../store/reducers/query/query'; import type {QueryInHistory} from '../../../../store/reducers/query/types'; +import {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory'; import {TENANT_QUERY_TABS_ID} from '../../../../store/reducers/tenant/constants'; import {setQueryTab} from '../../../../store/reducers/tenant/tenant'; import {cn} from '../../../../utils/cn'; @@ -34,9 +36,13 @@ interface QueriesHistoryProps { function QueriesHistory({changeUserInput}: QueriesHistoryProps) { const dispatch = useTypedDispatch(); - const queriesHistory = useTypedSelector(selectQueriesHistory); + const {filteredHistoryQueries} = useQueriesHistory(); + + const reversedHistory = React.useMemo(() => { + return [...filteredHistoryQueries].reverse(); + }, [filteredHistoryQueries]); + const filter = useTypedSelector(selectQueriesHistoryFilter); - const reversedHistory = [...queriesHistory].reverse(); const applyQueryClick = (query: QueryInHistory) => { changeUserInput({input: query.queryText}); diff --git a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx index a49e344919..c3d506d03e 100644 --- a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx @@ -11,15 +11,13 @@ import { } from '../../../../store/reducers/capabilities/hooks'; import { queryApi, - saveQueryToHistory, - selectQueriesHistory, - selectQueriesHistoryCurrentIndex, selectResult, selectTenantPath, setIsDirty, setTenantPath, } from '../../../../store/reducers/query/query'; import type {QueryResult} from '../../../../store/reducers/query/types'; +import {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory'; import {setQueryAction} from '../../../../store/reducers/queryActions/queryActions'; import {selectShowPreview, setShowPreview} from '../../../../store/reducers/schema/schema'; import {SETTING_KEYS} from '../../../../store/reducers/settings/constants'; @@ -77,10 +75,11 @@ export default function QueryEditor(props: QueryEditorProps) { const {theme, changeUserInput} = props; const savedPath = useTypedSelector(selectTenantPath); const result = useTypedSelector(selectResult); - const historyQueries = useTypedSelector(selectQueriesHistory); - const historyCurrentIndex = useTypedSelector(selectQueriesHistoryCurrentIndex); const showPreview = useTypedSelector(selectShowPreview); + const {historyQueries, historyCurrentIndex, saveQueryToHistory, updateQueryInHistory} = + useQueriesHistory(); + const isResultLoaded = Boolean(result); const [querySettings] = useQueryExecutionSettings(); @@ -182,6 +181,12 @@ export default function QueryEditor(props: QueryEditorProps) { base64: encodeTextWithBase64, }); + query.then(({data}) => { + if (data?.queryId) { + updateQueryInHistory(data.queryId, data?.queryStats); + } + }); + queryManagerInstance.registerQuery(query); } @@ -190,7 +195,7 @@ export default function QueryEditor(props: QueryEditorProps) { // Don't save partial queries in history if (!partial) { if (text !== historyQueries[historyCurrentIndex]?.queryText) { - dispatch(saveQueryToHistory({queryText: text, queryId})); + saveQueryToHistory(text, queryId); } dispatch(setIsDirty(false)); } diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index 2606f2b863..24e5e4fe8f 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -6,13 +6,8 @@ import throttle from 'lodash/throttle'; import type Monaco from 'monaco-editor'; import {MonacoEditor} from '../../../../components/MonacoEditor/MonacoEditor'; -import { - goToNextQuery, - goToPreviousQuery, - selectQueriesHistory, - selectUserInput, - setIsDirty, -} from '../../../../store/reducers/query/query'; +import {selectUserInput, setIsDirty} from '../../../../store/reducers/query/query'; +import {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory'; import {SETTING_KEYS} from '../../../../store/reducers/settings/constants'; import type {QueryAction} from '../../../../types/store/query'; import { @@ -49,7 +44,7 @@ export function YqlEditor({ const dispatch = useTypedDispatch(); const [monacoGhostInstance, setMonacoGhostInstance] = React.useState>(); - const historyQueries = useTypedSelector(selectQueriesHistory); + const {historyQueries, goToPreviousQuery, goToNextQuery} = useQueriesHistory(); const [isCodeAssistEnabled] = useSetting(SETTING_KEYS.ENABLE_CODE_ASSISTANT); const editorOptions = useEditorOptions(); @@ -160,7 +155,7 @@ export function YqlEditor({ contextMenuGroupId: CONTEXT_MENU_GROUP_ID, contextMenuOrder: 2, run: () => { - dispatch(goToPreviousQuery()); + goToPreviousQuery(); }, }); editor.addAction({ @@ -169,7 +164,7 @@ export function YqlEditor({ contextMenuGroupId: CONTEXT_MENU_GROUP_ID, contextMenuOrder: 3, run: () => { - dispatch(goToNextQuery()); + goToNextQuery(); }, }); editor.addAction({ diff --git a/src/containers/Tenant/Query/QueryEditor/helpers.ts b/src/containers/Tenant/Query/QueryEditor/helpers.ts index d9a23a2aec..4d0c1b1358 100644 --- a/src/containers/Tenant/Query/QueryEditor/helpers.ts +++ b/src/containers/Tenant/Query/QueryEditor/helpers.ts @@ -4,10 +4,10 @@ import type {AcceptEvent, DeclineEvent, IgnoreEvent, PromptFile} from '@ydb-plat import type Monaco from 'monaco-editor'; import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist'; -import {selectQueriesHistory} from '../../../../store/reducers/query/query'; +import {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory'; import {SETTING_KEYS} from '../../../../store/reducers/settings/constants'; import type {TelemetryOpenTabs} from '../../../../types/api/codeAssist'; -import {useSetting, useTypedSelector} from '../../../../utils/hooks'; +import {useSetting} from '../../../../utils/hooks'; import {YQL_LANGUAGE_ID} from '../../../../utils/monaco/constats'; import {useSavedQueries} from '../utils/useSavedQueries'; @@ -45,7 +45,7 @@ export function useCodeAssistHelpers() { const [discardSuggestion] = codeAssistApi.useDiscardSuggestionMutation(); const [ignoreSuggestion] = codeAssistApi.useIgnoreSuggestionMutation(); const [sendUserQueriesData] = codeAssistApi.useSendUserQueriesDataMutation(); - const historyQueries = useTypedSelector(selectQueriesHistory); + const {historyQueries} = useQueriesHistory(); const {savedQueries} = useSavedQueries(); const getCodeAssistSuggestions = React.useCallback( diff --git a/src/store/reducers/query/__test__/tabPersistence.test.tsx b/src/store/reducers/query/__test__/tabPersistence.test.tsx index d04a1f3026..b8bb2829e6 100644 --- a/src/store/reducers/query/__test__/tabPersistence.test.tsx +++ b/src/store/reducers/query/__test__/tabPersistence.test.tsx @@ -4,10 +4,6 @@ import type {QueryState} from '../types'; describe('QueryResultViewer tab persistence integration', () => { const initialState: QueryState = { input: '', - history: { - queries: [], - currentIndex: -1, - }, }; test('should save and retrieve tab selection for explain queries', () => { diff --git a/src/store/reducers/query/query.ts b/src/store/reducers/query/query.ts index 6895c4a423..2b31292d33 100644 --- a/src/store/reducers/query/query.ts +++ b/src/store/reducers/query/query.ts @@ -1,7 +1,6 @@ import {createSelector, createSlice} from '@reduxjs/toolkit'; import type {PayloadAction} from '@reduxjs/toolkit'; -import {settingsManager} from '../../../services/settings'; import {TracingLevelNumber} from '../../../types/api/query'; import type {QueryAction, QueryRequestParams, QuerySettings} from '../../../types/store/query'; import type {StreamDataChunk} from '../../../types/store/streaming'; @@ -11,7 +10,6 @@ import {isQueryErrorResponse} from '../../../utils/query'; import {isNumeric} from '../../../utils/utils'; import type {RootState} from '../../defaultStore'; import {api} from '../api'; -import {SETTING_KEYS} from '../settings/constants'; import {prepareQueryData} from './prepareQueryData'; import { @@ -19,17 +17,8 @@ import { setStreamQueryResponse as setStreamQueryResponseReducer, setStreamSession as setStreamSessionReducer, } from './streamingReducers'; -import type {QueryResult, QueryState} from './types'; -import {getActionAndSyntaxFromQueryMode, getQueryInHistory, prepareQueryWithPragmas} from './utils'; - -const MAXIMUM_QUERIES_IN_HISTORY = 20; - -const queriesHistoryInitial = settingsManager.readUserSettingsValue( - SETTING_KEYS.QUERIES_HISTORY, - [], -) as string[]; - -const sliceLimit = queriesHistoryInitial.length - MAXIMUM_QUERIES_IN_HISTORY; +import type {QueryResult, QueryState, QueryStats} from './types'; +import {getActionAndSyntaxFromQueryMode, prepareQueryWithPragmas} from './utils'; const rawQuery = loadFromSessionStorage(QUERY_EDITOR_CURRENT_QUERY_KEY); const input = typeof rawQuery === 'string' ? rawQuery : ''; @@ -39,16 +28,7 @@ const isDirty = Boolean(loadFromSessionStorage(QUERY_EDITOR_DIRTY_KEY)); const initialState: QueryState = { input, isDirty, - history: { - queries: queriesHistoryInitial - .slice(sliceLimit < 0 ? 0 : sliceLimit) - .map(getQueryInHistory), - currentIndex: - queriesHistoryInitial.length > MAXIMUM_QUERIES_IN_HISTORY - ? MAXIMUM_QUERIES_IN_HISTORY - 1 - : queriesHistoryInitial.length - 1, - filter: '', - }, + historyFilter: '', }; const slice = createSlice({ @@ -66,76 +46,11 @@ const slice = createSlice({ setQueryResult: (state, action: PayloadAction) => { state.result = action.payload; }, - saveQueryToHistory: ( - state, - action: PayloadAction<{queryText: string; queryId: string}>, - ) => { - const {queryText, queryId} = action.payload; - - const newQueries = [...state.history.queries, {queryText, queryId}].slice( - state.history.queries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0, - ); - settingsManager.setUserSettingsValue(SETTING_KEYS.QUERIES_HISTORY, newQueries); - const currentIndex = newQueries.length - 1; - - state.history = { - queries: newQueries, - currentIndex, - }; - }, - updateQueryInHistory: ( - state, - action: PayloadAction<{queryId: string; stats: QueryStats}>, - ) => { - const {queryId, stats} = action.payload; - - if (!stats) { - return; - } - - const index = state.history.queries.findIndex((item) => item.queryId === queryId); - - if (index === -1) { - return; - } - - const newQueries = [...state.history.queries]; - const {durationUs, endTime} = stats; - newQueries.splice(index, 1, { - ...state.history.queries[index], - durationUs, - endTime, - }); - - settingsManager.setUserSettingsValue(SETTING_KEYS.QUERIES_HISTORY, newQueries); - - state.history.queries = newQueries; - }, - goToPreviousQuery: (state) => { - const currentIndex = state.history.currentIndex; - if (currentIndex <= 0) { - return; - } - const newCurrentIndex = currentIndex - 1; - const query = state.history.queries[newCurrentIndex]; - state.input = query.queryText; - state.history.currentIndex = newCurrentIndex; - }, - goToNextQuery: (state) => { - const currentIndex = state.history.currentIndex; - if (currentIndex >= state.history.queries.length - 1) { - return; - } - const newCurrentIndex = currentIndex + 1; - const query = state.history.queries[newCurrentIndex]; - state.input = query.queryText; - state.history.currentIndex = newCurrentIndex; - }, setTenantPath: (state, action: PayloadAction) => { state.tenantPath = action.payload; }, setQueryHistoryFilter: (state, action: PayloadAction) => { - state.history.filter = action.payload; + state.historyFilter = action.payload; }, setResultTab: ( state, @@ -152,14 +67,13 @@ const slice = createSlice({ setStreamQueryResponse: setStreamQueryResponseReducer, }, selectors: { - selectQueriesHistoryFilter: (state) => state.history.filter || '', + selectQueriesHistoryFilter: (state) => state.historyFilter || '', selectTenantPath: (state) => state.tenantPath, selectResult: (state) => state.result, selectStartTime: (state) => state.result?.startTime, selectEndTime: (state) => state.result?.endTime, selectUserInput: (state) => state.input, selectIsDirty: (state) => state.isDirty, - selectQueriesHistoryCurrentIndex: (state) => state.history?.currentIndex, selectResultTab: (state) => state.selectedResultTab, }, }); @@ -175,27 +89,10 @@ export const selectQueryDuration = createSelector( }, ); -export const selectQueriesHistory = createSelector( - [ - (state: RootState) => state.query.history.queries, - (state: RootState) => state.query.history.filter, - ], - (queries, filter) => { - const normalizedFilter = filter?.toLowerCase(); - return normalizedFilter - ? queries.filter((item) => item.queryText.toLowerCase().includes(normalizedFilter)) - : queries; - }, -); - export default slice.reducer; export const { changeUserInput, setQueryResult, - saveQueryToHistory, - updateQueryInHistory, - goToPreviousQuery, - goToNextQuery, setTenantPath, setQueryHistoryFilter, addStreamingChunks, @@ -207,7 +104,6 @@ export const { export const { selectQueriesHistoryFilter, - selectQueriesHistoryCurrentIndex, selectTenantPath, selectResult, selectUserInput, @@ -228,11 +124,6 @@ interface SendQueryParams extends QueryRequestParams { // Stream query receives queryId from session chunk. type StreamQueryParams = Omit; -interface QueryStats { - durationUs?: string | number; - endTime?: string | number; -} - const DEFAULT_STREAM_CHUNK_SIZE = 1000; const DEFAULT_CONCURRENT_RESULTS = false; @@ -421,8 +312,9 @@ export const queryApi = api.injectEndpoints({ const data = prepareQueryData(response); data.traceId = response?._meta?.traceId; + const queryStats: QueryStats = {}; + if (actionType === 'execute') { - const queryStats: QueryStats = {}; if (data.stats) { const {DurationUs, Executions: [{FinishTimeMs}] = [{}]} = data.stats; queryStats.durationUs = DurationUs; @@ -432,8 +324,6 @@ export const queryApi = api.injectEndpoints({ queryStats.durationUs = (now - timeStart) * 1000; queryStats.endTime = now; } - - dispatch(updateQueryInHistory({stats: queryStats, queryId})); } dispatch( @@ -446,7 +336,7 @@ export const queryApi = api.injectEndpoints({ endTime: Date.now(), }), ); - return {data: null}; + return {data: {queryStats, queryId}}; } catch (error) { const state = getState() as RootState; if (state.query.result?.startTime !== startTime) { diff --git a/src/store/reducers/query/types.ts b/src/store/reducers/query/types.ts index 9d753b5210..e7975ab1eb 100644 --- a/src/store/reducers/query/types.ts +++ b/src/store/reducers/query/types.ts @@ -62,14 +62,15 @@ export interface QueryState { input: string; result?: QueryResult; isDirty?: boolean; - history: { - queries: QueryInHistory[]; - currentIndex: number; - filter?: string; - }; + historyFilter?: string; tenantPath?: string; selectedResultTab?: { execute?: string; explain?: string; }; } + +export interface QueryStats { + durationUs?: string | number; + endTime?: string | number; +} diff --git a/src/store/reducers/query/useQueriesHistory.ts b/src/store/reducers/query/useQueriesHistory.ts new file mode 100644 index 0000000000..f3287f1f6a --- /dev/null +++ b/src/store/reducers/query/useQueriesHistory.ts @@ -0,0 +1,123 @@ +import React from 'react'; + +import { + useEventHandler, + useSetting, + useTypedDispatch, + useTypedSelector, +} from '../../../utils/hooks'; +import {SETTING_KEYS} from '../settings/constants'; + +import {changeUserInput, selectQueriesHistoryFilter} from './query'; +import type {QueryInHistory, QueryStats} from './types'; +import {getQueryInHistory} from './utils'; + +const MAXIMUM_QUERIES_IN_HISTORY = 20; + +export function useQueriesHistory() { + const dispatch = useTypedDispatch(); + const queriesFilter = useTypedSelector(selectQueriesHistoryFilter); + + const [savedHistoryQueries, saveHistoryQueries] = useSetting( + SETTING_KEYS.QUERIES_HISTORY, + ); + + const [historyQueries, setQueries] = React.useState([]); + const [historyCurrentIndex, setCurrentIndex] = React.useState(-1); + + React.useEffect(() => { + if (!savedHistoryQueries || savedHistoryQueries.length === 0) { + setQueries([]); + setCurrentIndex(-1); + } else { + const sliceLimit = savedHistoryQueries.length - MAXIMUM_QUERIES_IN_HISTORY; + + const preparedQueries = savedHistoryQueries + .slice(sliceLimit < 0 ? 0 : sliceLimit) + .map(getQueryInHistory); + + setQueries(preparedQueries); + setCurrentIndex(preparedQueries.length - 1); + } + }, [savedHistoryQueries]); + + const filteredHistoryQueries = React.useMemo(() => { + const normalizedFilter = queriesFilter?.toLowerCase(); + return normalizedFilter + ? historyQueries.filter((item) => + item.queryText.toLowerCase().includes(normalizedFilter), + ) + : historyQueries; + }, [historyQueries, queriesFilter]); + + // These function are used inside Monaco editorDidMount + // They should be stable to work properly + const goToPreviousQuery = useEventHandler(() => { + setCurrentIndex((index) => { + if (historyQueries.length > 0 && index > 0) { + const newIndex = index - 1; + const query = historyQueries[newIndex]; + + if (query) { + dispatch(changeUserInput({input: query.queryText})); + return newIndex; + } + } + return index; + }); + }); + + const goToNextQuery = useEventHandler(() => { + setCurrentIndex((index) => { + if (historyQueries.length > 0 && index < historyQueries.length - 1) { + const newIndex = index + 1; + const query = historyQueries[newIndex]; + if (query) { + dispatch(changeUserInput({input: query.queryText})); + return newIndex; + } + } + + return index; + }); + }); + + const saveQueryToHistory = useEventHandler((queryText: string, queryId: string) => { + const newQueries = [...historyQueries, {queryText, queryId}].slice( + historyQueries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0, + ); + saveHistoryQueries(newQueries); + // Update currentIndex to point to the newly added query + const newCurrentIndex = newQueries.length - 1; + setCurrentIndex(newCurrentIndex); + }); + + const updateQueryInHistory = useEventHandler((queryId: string, stats: QueryStats) => { + if (!stats || !historyQueries.length) { + return; + } + + const index = historyQueries.findIndex((item) => item.queryId === queryId); + + if (index !== -1) { + const newQueries = [...historyQueries]; + const {durationUs, endTime} = stats; + newQueries.splice(index, 1, { + ...historyQueries[index], + durationUs, + endTime, + }); + saveHistoryQueries(newQueries); + } + }); + + return { + historyQueries, + historyCurrentIndex, + filteredHistoryQueries, + goToPreviousQuery, + goToNextQuery, + saveQueryToHistory, + updateQueryInHistory, + }; +} From 5eeadb309493f1bd1cbbb478a526e319a0a95855 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Tue, 18 Nov 2025 23:02:47 +0300 Subject: [PATCH 2/5] fix: copilot comments --- .../Tenant/Query/QueryEditor/QueryEditor.tsx | 14 +++++++++----- src/store/reducers/query/query.ts | 4 ++-- src/store/reducers/query/useQueriesHistory.ts | 4 +++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx index c3d506d03e..3d422b023b 100644 --- a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx @@ -181,11 +181,15 @@ export default function QueryEditor(props: QueryEditorProps) { base64: encodeTextWithBase64, }); - query.then(({data}) => { - if (data?.queryId) { - updateQueryInHistory(data.queryId, data?.queryStats); - } - }); + query + .then(({data}) => { + if (data?.queryId) { + updateQueryInHistory(data.queryId, data?.queryStats); + } + }) + .catch((error) => { + console.error('Failed to update query history:', error); + }); queryManagerInstance.registerQuery(query); } diff --git a/src/store/reducers/query/query.ts b/src/store/reducers/query/query.ts index 2b31292d33..99dfed1dde 100644 --- a/src/store/reducers/query/query.ts +++ b/src/store/reducers/query/query.ts @@ -236,7 +236,7 @@ export const queryApi = api.injectEndpoints({ } }, }), - useSendQuery: build.mutation({ + useSendQuery: build.mutation<{queryStats: QueryStats; queryId: string}, SendQueryParams>({ queryFn: async ( { actionType = 'execute', @@ -246,7 +246,7 @@ export const queryApi = api.injectEndpoints({ enableTracingLevel, queryId, base64, - }: SendQueryParams, + }, {signal, dispatch, getState}, ) => { const startTime = Date.now(); diff --git a/src/store/reducers/query/useQueriesHistory.ts b/src/store/reducers/query/useQueriesHistory.ts index f3287f1f6a..8b712e79fe 100644 --- a/src/store/reducers/query/useQueriesHistory.ts +++ b/src/store/reducers/query/useQueriesHistory.ts @@ -50,7 +50,7 @@ export function useQueriesHistory() { : historyQueries; }, [historyQueries, queriesFilter]); - // These function are used inside Monaco editorDidMount + // These functions are used inside Monaco editorDidMount // They should be stable to work properly const goToPreviousQuery = useEventHandler(() => { setCurrentIndex((index) => { @@ -87,6 +87,7 @@ export function useQueriesHistory() { historyQueries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0, ); saveHistoryQueries(newQueries); + setQueries(newQueries); // Update currentIndex to point to the newly added query const newCurrentIndex = newQueries.length - 1; setCurrentIndex(newCurrentIndex); @@ -108,6 +109,7 @@ export function useQueriesHistory() { endTime, }); saveHistoryQueries(newQueries); + setQueries(newQueries); } }); From 1f2e37ef0ce966332813762f97086fb3e43176fd Mon Sep 17 00:00:00 2001 From: mufazalov Date: Thu, 20 Nov 2025 00:19:46 +0300 Subject: [PATCH 3/5] fix: copilot review --- .../Tenant/Query/QueryEditor/QueryEditor.tsx | 14 ++++- .../Tenant/Query/QueryEditor/YqlEditor.tsx | 11 ++-- .../Tenant/Query/QueryEditor/helpers.ts | 5 +- src/store/reducers/query/useQueriesHistory.ts | 53 +++++++++++-------- 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx index 3d422b023b..dd97429b8e 100644 --- a/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx @@ -77,8 +77,14 @@ export default function QueryEditor(props: QueryEditorProps) { const result = useTypedSelector(selectResult); const showPreview = useTypedSelector(selectShowPreview); - const {historyQueries, historyCurrentIndex, saveQueryToHistory, updateQueryInHistory} = - useQueriesHistory(); + const { + historyQueries, + historyCurrentIndex, + saveQueryToHistory, + updateQueryInHistory, + goToPreviousQuery, + goToNextQuery, + } = useQueriesHistory(); const isResultLoaded = Boolean(result); @@ -188,6 +194,7 @@ export default function QueryEditor(props: QueryEditorProps) { } }) .catch((error) => { + // Do not add query stats for failed query console.error('Failed to update query history:', error); }); @@ -288,6 +295,9 @@ export default function QueryEditor(props: QueryEditorProps) { theme={theme} handleSendExecuteClick={handleSendExecuteClick} handleGetExplainQueryClick={handleGetExplainQueryClick} + historyQueries={historyQueries} + goToPreviousQuery={goToPreviousQuery} + goToNextQuery={goToNextQuery} /> diff --git a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx index 24e5e4fe8f..0954658ec6 100644 --- a/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx +++ b/src/containers/Tenant/Query/QueryEditor/YqlEditor.tsx @@ -7,7 +7,7 @@ import type Monaco from 'monaco-editor'; import {MonacoEditor} from '../../../../components/MonacoEditor/MonacoEditor'; import {selectUserInput, setIsDirty} from '../../../../store/reducers/query/query'; -import {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory'; +import type {QueryInHistory} from '../../../../store/reducers/query/types'; import {SETTING_KEYS} from '../../../../store/reducers/settings/constants'; import type {QueryAction} from '../../../../types/store/query'; import { @@ -32,6 +32,9 @@ interface YqlEditorProps { theme: string; handleGetExplainQueryClick: (text: string) => void; handleSendExecuteClick: (text: string, partial?: boolean) => void; + historyQueries: QueryInHistory[]; + goToPreviousQuery: () => void; + goToNextQuery: () => void; } export function YqlEditor({ @@ -39,12 +42,14 @@ export function YqlEditor({ theme, handleSendExecuteClick, handleGetExplainQueryClick, + historyQueries, + goToPreviousQuery, + goToNextQuery, }: YqlEditorProps) { const input = useTypedSelector(selectUserInput); const dispatch = useTypedDispatch(); const [monacoGhostInstance, setMonacoGhostInstance] = React.useState>(); - const {historyQueries, goToPreviousQuery, goToNextQuery} = useQueriesHistory(); const [isCodeAssistEnabled] = useSetting(SETTING_KEYS.ENABLE_CODE_ASSISTANT); const editorOptions = useEditorOptions(); @@ -71,7 +76,7 @@ export function YqlEditor({ window.ydbEditor = undefined; }; - const {monacoGhostConfig, prepareUserQueriesCache} = useCodeAssistHelpers(); + const {monacoGhostConfig, prepareUserQueriesCache} = useCodeAssistHelpers(historyQueries); React.useEffect(() => { if (monacoGhostInstance && isCodeAssistEnabled) { diff --git a/src/containers/Tenant/Query/QueryEditor/helpers.ts b/src/containers/Tenant/Query/QueryEditor/helpers.ts index 4d0c1b1358..3bb7efb8fb 100644 --- a/src/containers/Tenant/Query/QueryEditor/helpers.ts +++ b/src/containers/Tenant/Query/QueryEditor/helpers.ts @@ -4,7 +4,7 @@ import type {AcceptEvent, DeclineEvent, IgnoreEvent, PromptFile} from '@ydb-plat import type Monaco from 'monaco-editor'; import {codeAssistApi} from '../../../../store/reducers/codeAssist/codeAssist'; -import {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory'; +import type {QueryInHistory} from '../../../../store/reducers/query/types'; import {SETTING_KEYS} from '../../../../store/reducers/settings/constants'; import type {TelemetryOpenTabs} from '../../../../types/api/codeAssist'; import {useSetting} from '../../../../utils/hooks'; @@ -39,13 +39,12 @@ export function useEditorOptions() { return options; } -export function useCodeAssistHelpers() { +export function useCodeAssistHelpers(historyQueries: QueryInHistory[]) { const [sendCodeAssistPrompt] = codeAssistApi.useLazyGetCodeAssistSuggestionsQuery(); const [acceptSuggestion] = codeAssistApi.useAcceptSuggestionMutation(); const [discardSuggestion] = codeAssistApi.useDiscardSuggestionMutation(); const [ignoreSuggestion] = codeAssistApi.useIgnoreSuggestionMutation(); const [sendUserQueriesData] = codeAssistApi.useSendUserQueriesDataMutation(); - const {historyQueries} = useQueriesHistory(); const {savedQueries} = useSavedQueries(); const getCodeAssistSuggestions = React.useCallback( diff --git a/src/store/reducers/query/useQueriesHistory.ts b/src/store/reducers/query/useQueriesHistory.ts index 8b712e79fe..b157e59c48 100644 --- a/src/store/reducers/query/useQueriesHistory.ts +++ b/src/store/reducers/query/useQueriesHistory.ts @@ -83,34 +83,45 @@ export function useQueriesHistory() { }); const saveQueryToHistory = useEventHandler((queryText: string, queryId: string) => { - const newQueries = [...historyQueries, {queryText, queryId}].slice( - historyQueries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0, - ); - saveHistoryQueries(newQueries); - setQueries(newQueries); - // Update currentIndex to point to the newly added query - const newCurrentIndex = newQueries.length - 1; - setCurrentIndex(newCurrentIndex); + setQueries((currentQueries) => { + const newQueries = [...currentQueries, {queryText, queryId}].slice( + historyQueries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0, + ); + saveHistoryQueries(newQueries); + + // Update currentIndex to point to the newly added query + const newCurrentIndex = newQueries.length - 1; + setCurrentIndex(newCurrentIndex); + + return newQueries; + }); }); const updateQueryInHistory = useEventHandler((queryId: string, stats: QueryStats) => { - if (!stats || !historyQueries.length) { + if (!stats) { return; } - const index = historyQueries.findIndex((item) => item.queryId === queryId); + setQueries((currentQueries) => { + if (!currentQueries.length) { + return currentQueries; + } - if (index !== -1) { - const newQueries = [...historyQueries]; - const {durationUs, endTime} = stats; - newQueries.splice(index, 1, { - ...historyQueries[index], - durationUs, - endTime, - }); - saveHistoryQueries(newQueries); - setQueries(newQueries); - } + const index = currentQueries.findIndex((item) => item.queryId === queryId); + + if (index !== -1) { + const newQueries = [...currentQueries]; + const {durationUs, endTime} = stats; + newQueries.splice(index, 1, { + ...currentQueries[index], + durationUs, + endTime, + }); + saveHistoryQueries(newQueries); + return newQueries; + } + return currentQueries; + }); }); return { From 31777916470489bd54661419f3529467a1daa457 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Thu, 20 Nov 2025 00:36:56 +0300 Subject: [PATCH 4/5] fix: copilot review 3 --- src/store/reducers/query/useQueriesHistory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/store/reducers/query/useQueriesHistory.ts b/src/store/reducers/query/useQueriesHistory.ts index b157e59c48..8c536c1349 100644 --- a/src/store/reducers/query/useQueriesHistory.ts +++ b/src/store/reducers/query/useQueriesHistory.ts @@ -85,7 +85,7 @@ export function useQueriesHistory() { const saveQueryToHistory = useEventHandler((queryText: string, queryId: string) => { setQueries((currentQueries) => { const newQueries = [...currentQueries, {queryText, queryId}].slice( - historyQueries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0, + currentQueries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0, ); saveHistoryQueries(newQueries); From c1313aa63622828606ec7b149074cdbd292900c7 Mon Sep 17 00:00:00 2001 From: mufazalov Date: Thu, 20 Nov 2025 14:59:43 +0300 Subject: [PATCH 5/5] fix: toReversed --- src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx b/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx index 76393b40d0..e5cc8b0e0e 100644 --- a/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx +++ b/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx @@ -39,7 +39,7 @@ function QueriesHistory({changeUserInput}: QueriesHistoryProps) { const {filteredHistoryQueries} = useQueriesHistory(); const reversedHistory = React.useMemo(() => { - return [...filteredHistoryQueries].reverse(); + return filteredHistoryQueries.toReversed(); }, [filteredHistoryQueries]); const filter = useTypedSelector(selectQueriesHistoryFilter);