Skip to content

Commit 0b6421a

Browse files
fix: use query id instead of indexes to navigate in history
1 parent afaf736 commit 0b6421a

File tree

7 files changed

+136
-92
lines changed

7 files changed

+136
-92
lines changed

src/containers/Tenant/Query/QueriesHistory/QueriesHistory.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
setQueryHistoryFilter,
1313
} from '../../../../store/reducers/query/query';
1414
import type {QueryInHistory} from '../../../../store/reducers/query/types';
15-
import {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory';
15+
import type {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory';
1616
import {TENANT_QUERY_TABS_ID} from '../../../../store/reducers/tenant/constants';
1717
import {setQueryTab} from '../../../../store/reducers/tenant/tenant';
1818
import {cn} from '../../../../utils/cn';
@@ -31,16 +31,15 @@ const QUERIES_HISTORY_COLUMNS_WIDTH_LS_KEY = 'queriesHistoryTableColumnsWidth';
3131

3232
interface QueriesHistoryProps {
3333
changeUserInput: (value: {input: string}) => void;
34+
queriesHistory: ReturnType<typeof useQueriesHistory>;
3435
}
3536

36-
function QueriesHistory({changeUserInput}: QueriesHistoryProps) {
37+
function QueriesHistory({changeUserInput, queriesHistory}: QueriesHistoryProps) {
3738
const dispatch = useTypedDispatch();
3839

39-
const {filteredHistoryQueries} = useQueriesHistory();
40-
4140
const reversedHistory = React.useMemo(() => {
42-
return filteredHistoryQueries.toReversed();
43-
}, [filteredHistoryQueries]);
41+
return queriesHistory.filteredHistoryQueries.toReversed();
42+
}, [queriesHistory.filteredHistoryQueries]);
4443

4544
const filter = useTypedSelector(selectQueriesHistoryFilter);
4645

src/containers/Tenant/Query/Query.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import React from 'react';
33
import {Helmet} from 'react-helmet-async';
44

55
import {changeUserInput} from '../../../store/reducers/query/query';
6+
import {useQueriesHistory} from '../../../store/reducers/query/useQueriesHistory';
67
import {TENANT_QUERY_TABS_ID} from '../../../store/reducers/tenant/constants';
78
import {cn} from '../../../utils/cn';
89
import {useTypedDispatch, useTypedSelector} from '../../../utils/hooks';
@@ -25,6 +26,8 @@ export const Query = (props: QueryProps) => {
2526

2627
const {queryTab = TENANT_QUERY_TABS_ID.newQuery} = useTypedSelector((state) => state.tenant);
2728

29+
const queriesHistory = useQueriesHistory();
30+
2831
const handleUserInputChange = (value: {input: string}) => {
2932
dispatch(changeUserInput(value));
3033
};
@@ -37,10 +40,21 @@ export const Query = (props: QueryProps) => {
3740
const renderContent = () => {
3841
switch (queryTab) {
3942
case TENANT_QUERY_TABS_ID.newQuery: {
40-
return <QueryEditor changeUserInput={handleUserInputChange} {...props} />;
43+
return (
44+
<QueryEditor
45+
changeUserInput={handleUserInputChange}
46+
queriesHistory={queriesHistory}
47+
{...props}
48+
/>
49+
);
4150
}
4251
case TENANT_QUERY_TABS_ID.history: {
43-
return <QueriesHistory changeUserInput={handleUserInputChange} />;
52+
return (
53+
<QueriesHistory
54+
changeUserInput={handleUserInputChange}
55+
queriesHistory={queriesHistory}
56+
/>
57+
);
4458
}
4559
case TENANT_QUERY_TABS_ID.saved: {
4660
return <SavedQueries changeUserInput={handleUserInputChange} />;

src/containers/Tenant/Query/QueryEditor/QueryEditor.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
setTenantPath,
1818
} from '../../../../store/reducers/query/query';
1919
import type {QueryResult} from '../../../../store/reducers/query/types';
20-
import {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory';
20+
import type {useQueriesHistory} from '../../../../store/reducers/query/useQueriesHistory';
2121
import {setQueryAction} from '../../../../store/reducers/queryActions/queryActions';
2222
import {selectShowPreview, setShowPreview} from '../../../../store/reducers/schema/schema';
2323
import {SETTING_KEYS} from '../../../../store/reducers/settings/constants';
@@ -67,24 +67,24 @@ const initialTenantCommonInfoState = {
6767
interface QueryEditorProps {
6868
changeUserInput: (arg: {input: string}) => void;
6969
theme: string;
70+
queriesHistory: ReturnType<typeof useQueriesHistory>;
7071
}
7172

72-
export default function QueryEditor(props: QueryEditorProps) {
73+
export default function QueryEditor({theme, changeUserInput, queriesHistory}: QueryEditorProps) {
7374
const dispatch = useTypedDispatch();
7475
const {database, path, type, subType, databaseFullPath} = useCurrentSchema();
75-
const {theme, changeUserInput} = props;
7676
const savedPath = useTypedSelector(selectTenantPath);
7777
const result = useTypedSelector(selectResult);
7878
const showPreview = useTypedSelector(selectShowPreview);
7979

8080
const {
8181
historyQueries,
82-
historyCurrentIndex,
82+
historyCurrentQueryId,
8383
saveQueryToHistory,
8484
updateQueryInHistory,
8585
goToPreviousQuery,
8686
goToNextQuery,
87-
} = useQueriesHistory();
87+
} = queriesHistory;
8888

8989
const isResultLoaded = Boolean(result);
9090

@@ -205,7 +205,10 @@ export default function QueryEditor(props: QueryEditorProps) {
205205

206206
// Don't save partial queries in history
207207
if (!partial) {
208-
if (text !== historyQueries[historyCurrentIndex]?.queryText) {
208+
const currentQuery = historyCurrentQueryId
209+
? historyQueries.find((q) => q.queryId === historyCurrentQueryId)
210+
: null;
211+
if (text !== currentQuery?.queryText) {
209212
saveQueryToHistory(text, queryId);
210213
}
211214
dispatch(setIsDirty(false));

src/store/reducers/query/query.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ const isDirty = Boolean(loadFromSessionStorage(QUERY_EDITOR_DIRTY_KEY));
2828
const initialState: QueryState = {
2929
input,
3030
isDirty,
31+
3132
historyFilter: '',
33+
historyCurrentQueryId: undefined,
3234
};
3335

3436
const slice = createSlice({
@@ -52,6 +54,9 @@ const slice = createSlice({
5254
setQueryHistoryFilter: (state, action: PayloadAction<string>) => {
5355
state.historyFilter = action.payload;
5456
},
57+
setHistoryCurrentQueryId: (state, action: PayloadAction<string | undefined>) => {
58+
state.historyCurrentQueryId = action.payload;
59+
},
5560
setResultTab: (
5661
state,
5762
action: PayloadAction<{queryType: 'execute' | 'explain'; tabId: string}>,
@@ -68,6 +73,7 @@ const slice = createSlice({
6873
},
6974
selectors: {
7075
selectQueriesHistoryFilter: (state) => state.historyFilter || '',
76+
selectHistoryCurrentQueryId: (state) => state.historyCurrentQueryId,
7177
selectTenantPath: (state) => state.tenantPath,
7278
selectResult: (state) => state.result,
7379
selectStartTime: (state) => state.result?.startTime,
@@ -95,6 +101,7 @@ export const {
95101
setQueryResult,
96102
setTenantPath,
97103
setQueryHistoryFilter,
104+
setHistoryCurrentQueryId,
98105
addStreamingChunks,
99106
setStreamQueryResponse,
100107
setStreamSession,
@@ -104,6 +111,7 @@ export const {
104111

105112
export const {
106113
selectQueriesHistoryFilter,
114+
selectHistoryCurrentQueryId,
107115
selectTenantPath,
108116
selectResult,
109117
selectUserInput,

src/store/reducers/query/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ export interface QueryState {
6262
input: string;
6363
result?: QueryResult;
6464
isDirty?: boolean;
65+
6566
historyFilter?: string;
67+
historyCurrentQueryId?: string;
68+
6669
tenantPath?: string;
6770
selectedResultTab?: {
6871
execute?: string;

src/store/reducers/query/useQueriesHistory.ts

Lines changed: 84 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@ import {
88
} from '../../../utils/hooks';
99
import {SETTING_KEYS} from '../settings/constants';
1010

11-
import {changeUserInput, selectQueriesHistoryFilter} from './query';
11+
import {
12+
changeUserInput,
13+
selectHistoryCurrentQueryId,
14+
selectQueriesHistoryFilter,
15+
setHistoryCurrentQueryId,
16+
} from './query';
1217
import type {QueryInHistory, QueryStats} from './types';
1318
import {getQueryInHistory} from './utils';
1419

@@ -17,116 +22,118 @@ const MAXIMUM_QUERIES_IN_HISTORY = 20;
1722
export function useQueriesHistory() {
1823
const dispatch = useTypedDispatch();
1924
const queriesFilter = useTypedSelector(selectQueriesHistoryFilter);
25+
const historyCurrentQueryId = useTypedSelector(selectHistoryCurrentQueryId);
2026

2127
const [savedHistoryQueries, saveHistoryQueries] = useSetting<QueryInHistory[]>(
2228
SETTING_KEYS.QUERIES_HISTORY,
2329
);
2430

25-
const [historyQueries, setQueries] = React.useState<QueryInHistory[]>([]);
26-
const [historyCurrentIndex, setCurrentIndex] = React.useState(-1);
27-
28-
React.useEffect(() => {
29-
if (!savedHistoryQueries || savedHistoryQueries.length === 0) {
30-
setQueries([]);
31-
setCurrentIndex(-1);
32-
} else {
33-
const sliceLimit = savedHistoryQueries.length - MAXIMUM_QUERIES_IN_HISTORY;
34-
35-
const preparedQueries = savedHistoryQueries
36-
.slice(sliceLimit < 0 ? 0 : sliceLimit)
37-
.map(getQueryInHistory);
31+
const preparedQueries = React.useMemo(() => {
32+
const queries = savedHistoryQueries ?? [];
3833

39-
setQueries(preparedQueries);
40-
setCurrentIndex(preparedQueries.length - 1);
41-
}
34+
return queries.map(getQueryInHistory);
4235
}, [savedHistoryQueries]);
4336

4437
const filteredHistoryQueries = React.useMemo(() => {
4538
const normalizedFilter = queriesFilter?.toLowerCase();
4639
return normalizedFilter
47-
? historyQueries.filter((item) =>
40+
? preparedQueries.filter((item) =>
4841
item.queryText.toLowerCase().includes(normalizedFilter),
4942
)
50-
: historyQueries;
51-
}, [historyQueries, queriesFilter]);
43+
: preparedQueries;
44+
}, [preparedQueries, queriesFilter]);
5245

5346
// These functions are used inside Monaco editorDidMount
5447
// They should be stable to work properly
5548
const goToPreviousQuery = useEventHandler(() => {
56-
setCurrentIndex((index) => {
57-
if (historyQueries.length > 0 && index > 0) {
58-
const newIndex = index - 1;
59-
const query = historyQueries[newIndex];
60-
61-
if (query) {
62-
dispatch(changeUserInput({input: query.queryText}));
63-
return newIndex;
64-
}
65-
}
66-
return index;
67-
});
49+
if (preparedQueries.length === 0) {
50+
return;
51+
}
52+
53+
let query: QueryInHistory | undefined;
54+
55+
const currentIndex = preparedQueries.findIndex((q) => q.queryId === historyCurrentQueryId);
56+
57+
if (currentIndex === -1) {
58+
// Query not found, start from last
59+
query = preparedQueries.at(-1);
60+
} else if (currentIndex === 0) {
61+
// At first query, wrap to last
62+
query = preparedQueries.at(-1);
63+
} else {
64+
// Go to previous query
65+
query = preparedQueries[currentIndex - 1];
66+
}
67+
68+
if (query) {
69+
dispatch(changeUserInput({input: query.queryText}));
70+
dispatch(setHistoryCurrentQueryId(query.queryId));
71+
}
6872
});
6973

7074
const goToNextQuery = useEventHandler(() => {
71-
setCurrentIndex((index) => {
72-
if (historyQueries.length > 0 && index < historyQueries.length - 1) {
73-
const newIndex = index + 1;
74-
const query = historyQueries[newIndex];
75-
if (query) {
76-
dispatch(changeUserInput({input: query.queryText}));
77-
return newIndex;
78-
}
79-
}
80-
81-
return index;
82-
});
75+
if (preparedQueries.length === 0) {
76+
return;
77+
}
78+
79+
let query: QueryInHistory | undefined;
80+
81+
const currentIndex = preparedQueries.findIndex((q) => q.queryId === historyCurrentQueryId);
82+
83+
if (currentIndex === -1) {
84+
// Query not found, start from last
85+
query = preparedQueries.at(-1);
86+
} else if (currentIndex === preparedQueries.length - 1) {
87+
// At last query, wrap to first
88+
query = preparedQueries[0];
89+
} else {
90+
// Go to next query
91+
query = preparedQueries[currentIndex + 1];
92+
}
93+
94+
if (query) {
95+
dispatch(changeUserInput({input: query.queryText}));
96+
dispatch(setHistoryCurrentQueryId(query.queryId));
97+
}
8398
});
8499

85100
const saveQueryToHistory = useEventHandler((queryText: string, queryId: string) => {
86-
setQueries((currentQueries) => {
87-
const newQueries = [...currentQueries, {queryText, queryId}].slice(
88-
currentQueries.length >= MAXIMUM_QUERIES_IN_HISTORY ? 1 : 0,
89-
);
90-
saveHistoryQueries(newQueries);
101+
if (!queryText) {
102+
return;
103+
}
104+
// +1 to include new query
105+
const sliceLimit = preparedQueries.length + 1 - MAXIMUM_QUERIES_IN_HISTORY;
106+
const slicedQueries = preparedQueries.slice(sliceLimit < 0 ? 0 : sliceLimit);
107+
const newQueries = [...slicedQueries, {queryText, queryId}];
91108

92-
// Update currentIndex to point to the newly added query
93-
const newCurrentIndex = newQueries.length - 1;
94-
setCurrentIndex(newCurrentIndex);
109+
saveHistoryQueries(newQueries);
95110

96-
return newQueries;
97-
});
111+
// Update currentQueryId to point to the newly added query
112+
dispatch(setHistoryCurrentQueryId(queryId));
98113
});
99114

100115
const updateQueryInHistory = useEventHandler((queryId: string, stats: QueryStats) => {
101-
if (!stats) {
116+
if (!stats || !preparedQueries.length) {
102117
return;
103118
}
104119

105-
setQueries((currentQueries) => {
106-
if (!currentQueries.length) {
107-
return currentQueries;
108-
}
109-
110-
const index = currentQueries.findIndex((item) => item.queryId === queryId);
111-
112-
if (index !== -1) {
113-
const newQueries = [...currentQueries];
114-
const {durationUs, endTime} = stats;
115-
newQueries.splice(index, 1, {
116-
...currentQueries[index],
117-
durationUs,
118-
endTime,
119-
});
120-
saveHistoryQueries(newQueries);
121-
return newQueries;
122-
}
123-
return currentQueries;
124-
});
120+
const index = preparedQueries.findIndex((item) => item.queryId === queryId);
121+
122+
if (index !== -1) {
123+
const newQueries = [...preparedQueries];
124+
const {durationUs, endTime} = stats;
125+
newQueries[index] = {
126+
...preparedQueries[index],
127+
durationUs,
128+
endTime,
129+
};
130+
saveHistoryQueries(newQueries);
131+
}
125132
});
126133

127134
return {
128-
historyQueries,
129-
historyCurrentIndex,
135+
historyQueries: preparedQueries,
136+
historyCurrentQueryId,
130137
filteredHistoryQueries,
131138
goToPreviousQuery,
132139
goToNextQuery,

0 commit comments

Comments
 (0)