Skip to content

Commit c0d9965

Browse files
authored
chore(webview): NoSQL Query History is persisted (#2640)
2 parents 1ffc88b + 6ca5a69 commit c0d9965

4 files changed

Lines changed: 68 additions & 15 deletions

File tree

src/panels/QueryEditorTab.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@
55

66
import { type PartitionKeyDefinition } from '@azure/cosmos';
77
import { callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils';
8+
import * as l10n from '@vscode/l10n';
89
import * as crypto from 'crypto';
910
import * as vscode from 'vscode';
10-
11-
import * as l10n from '@vscode/l10n';
1211
import { getCosmosDBClientByConnection, getCosmosDBKeyCredential } from '../cosmosdb/getCosmosClient';
1312
import { type NoSqlQueryConnection } from '../cosmosdb/NoSqlCodeLensProvider';
1413
import { DocumentSession } from '../cosmosdb/session/DocumentSession';
@@ -19,13 +18,23 @@ import {
1918
type SerializedQueryResult,
2019
} from '../cosmosdb/types/queryResult';
2120
import { getNoSqlQueryConnection } from '../cosmosdb/utils/NoSqlQueryConnection';
21+
import { StorageNames, StorageService, type StorageItem } from '../services/storageService';
2222
import { queryMetricsToCsv, queryResultToCsv } from '../utils/csvConverter';
2323
import { getIsSurveyDisabledGlobally, openSurvey, promptAfterActionEventually } from '../utils/survey';
2424
import { ExperienceKind, UsageImpact } from '../utils/surveyTypes';
2525
import * as vscodeUtil from '../utils/vscodeUtils';
2626
import { BaseTab, type CommandPayload } from './BaseTab';
2727
import { DocumentTab } from './DocumentTab';
2828

29+
const QUERY_HISTORY_SIZE = 10;
30+
const HISTORY_STORAGE_KEY = 'ms-azuretools.vscode-cosmosdb.history';
31+
32+
type HistoryItem = StorageItem & {
33+
properties: {
34+
history: string[];
35+
};
36+
};
37+
2938
export class QueryEditorTab extends BaseTab {
3039
public static readonly title = 'Query Editor';
3140
public static readonly viewType = 'cosmosDbQuery';
@@ -98,6 +107,7 @@ export class QueryEditorTab extends BaseTab {
98107

99108
this.channel.on<void>('ready', async () => {
100109
await this.updateConnection(this.connection);
110+
await this.updateQueryHistory();
101111
if (this.query) {
102112
await this.channel.postMessage({
103113
type: 'event',
@@ -168,6 +178,8 @@ export class QueryEditorTab extends BaseTab {
168178
);
169179
case 'copyMetricsCSVToClipboard':
170180
return this.copyMetricsCSVToClipboard(payload.params[0] as SerializedQueryResult | null);
181+
case 'updateQueryHistory':
182+
return this.updateQueryHistory(payload.params[0] as string);
171183
}
172184

173185
return super.getCommand(payload);
@@ -257,6 +269,50 @@ export class QueryEditorTab extends BaseTab {
257269
});
258270
}
259271

272+
private async updateQueryHistory(query?: string): Promise<void> {
273+
await callWithTelemetryAndErrorHandling('cosmosDB.nosql.queryEditor.updateQueryHistory', async (context) => {
274+
context.telemetry.suppressIfSuccessful = true;
275+
276+
if (!this.connection) {
277+
throw new Error(l10n.t('No connection'));
278+
}
279+
280+
const storage = StorageService.get(StorageNames.Default);
281+
const containerId = `${this.connection.databaseId}/${this.connection.containerId}`;
282+
const historyItems = (await storage.getItems(HISTORY_STORAGE_KEY)) as HistoryItem[];
283+
const historyData = historyItems.find((item) => item.id === containerId) ?? {
284+
id: containerId,
285+
name: containerId,
286+
properties: {
287+
history: [] as string[],
288+
},
289+
};
290+
291+
// First remove any existing occurrences of this query
292+
const queryHistory = historyData.properties.history.filter((item) => item !== query);
293+
294+
// Add the new query to the beginning (most recent first)
295+
if (query) {
296+
queryHistory.unshift(query);
297+
}
298+
299+
// Trim to max size if needed
300+
if (queryHistory.length > QUERY_HISTORY_SIZE) {
301+
queryHistory.length = QUERY_HISTORY_SIZE;
302+
}
303+
304+
historyData.properties.history = queryHistory;
305+
await storage.push(HISTORY_STORAGE_KEY, historyData);
306+
307+
// Update the webview with the new history
308+
await this.channel.postMessage({
309+
type: 'event',
310+
name: 'updateQueryHistory',
311+
params: [historyData.properties.history],
312+
});
313+
});
314+
}
315+
260316
private async duplicateTab(text: string): Promise<void> {
261317
await callWithTelemetryAndErrorHandling('cosmosDB.nosql.queryEditor.duplicateTab', () => {
262318
QueryEditorTab.render(this.connection, this.panel.viewColumn, false, text);

src/services/storageService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export type StorageItem = {
2626
/**
2727
* Optional properties associated with the item.
2828
*/
29-
properties?: Record<string, string | boolean>;
29+
properties?: Record<string, string[] | string | boolean>;
3030

3131
/**
3232
* Optional array of secrets associated with the item.

src/webviews/cosmosdb/QueryEditor/state/QueryEditorContextProvider.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export class QueryEditorContextProvider extends BaseContextProvider {
3131
}
3232

3333
public async runQuery(query: string, options: ResultViewMetadata): Promise<void> {
34-
this.dispatch({ type: 'appendQueryHistory', queryValue: query });
34+
await this.sendCommand('updateQueryHistory', query);
3535
await this.sendCommand('runQuery', query, { ...DEFAULT_RESULT_VIEW_METADATA, ...options });
3636
}
3737
public async stopQuery(executionId: string): Promise<void> {
@@ -166,6 +166,10 @@ export class QueryEditorContextProvider extends BaseContextProvider {
166166
this.dispatch({ type: 'setIsSurveyCandidate', isSurveyCandidate: isSurveyCandidate });
167167
});
168168

169+
this.channel.on('updateQueryHistory', (queryHistory: string[]) => {
170+
this.dispatch({ type: 'updateHistory', queryHistory });
171+
});
172+
169173
//TODO: there should be no queryError event that needs to show a toast,
170174
// all errors should be handled by QuerySession and dispatched to host error handling.
171175
// eslint-disable-next-line @typescript-eslint/no-unused-vars

src/webviews/cosmosdb/QueryEditor/state/QueryEditorState.tsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ export type DispatchAction =
3636
endExecutionTime: number;
3737
}
3838
| {
39-
type: 'appendQueryHistory';
40-
queryValue: string;
39+
type: 'updateHistory';
40+
queryHistory: string[];
4141
}
4242
| {
4343
type: 'setPageSize';
@@ -146,15 +146,8 @@ export function dispatch(state: QueryEditorState, action: DispatchAction): Query
146146
}
147147
return { ...state, isExecuting: false, endExecutionTime: action.endExecutionTime };
148148
}
149-
case 'appendQueryHistory': {
150-
const queryHistory = [...state.queryHistory, action.queryValue].filter(
151-
(value, index, self) => self.indexOf(value) === index,
152-
);
153-
if (queryHistory.length > QUERY_HISTORY_SIZE) {
154-
queryHistory.shift();
155-
}
156-
return { ...state, queryHistory };
157-
}
149+
case 'updateHistory':
150+
return { ...state, queryHistory: action.queryHistory };
158151
case 'setPageSize':
159152
return { ...state, pageSize: action.pageSize };
160153
case 'updateQueryResult':

0 commit comments

Comments
 (0)