* initial query history working

* added context menu actions and empty node

* added clear, empty node and sorting of history

* change default limit to 20

* added delete method

* implemented open query

* fixed tests

* implemented run query

* added setting to capture query history

* added button to start/pause query history capture

* fixed clauses for command palette

* added query history UI in command palette

* finished command palette UI

* review comments

* remove the node itself

* put clear all in view action

* fix tooltip order

* changed icons, added user setting for feature

* fix tooltip and text for nodes

* fixed labels for node

* explicit check for no selection

* more cleanup

* code review comments

* change command string
This commit is contained in:
Aditya Bist 2020-01-27 17:08:11 -08:00 коммит произвёл GitHub
Родитель 0f52dfb95a
Коммит 4eeba6e3e0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
20 изменённых файлов: 854 добавлений и 179 удалений

Просмотреть файл

@ -36,13 +36,21 @@ gulp.task('ext:lint', () => {
});
// Copy icons for OE
gulp.task('ext:copy-assets', (done) => {
gulp.task('ext:copy-OE-assets', (done) => {
return gulp.src([
config.paths.project.root + '/src/objectExplorer/objectTypes/*'
])
.pipe(gulp.dest('out/src/objectExplorer/objectTypes'));
});
// Copy icons for Query History
gulp.task('ext:copy-queryHistory-assets', (done) => {
return gulp.src([
config.paths.project.root + '/src/queryHistory/icons/*'
])
.pipe(gulp.dest('out/src/queryHistory/icons'));
});
// Compile source
gulp.task('ext:compile-src', (done) => {
return gulp.src([
@ -208,7 +216,7 @@ gulp.task('ext:compile-tests', (done) => {
});
gulp.task('ext:compile', gulp.series('ext:compile-src', 'ext:compile-tests', 'ext:copy-assets'));
gulp.task('ext:compile', gulp.series('ext:compile-src', 'ext:compile-tests', 'ext:copy-OE-assets', 'ext:copy-queryHistory-assets'));
gulp.task('ext:copy-tests', () => {
return gulp.src(config.paths.project.root + '/test/resources/**/*')

Просмотреть файл

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-green{fill:#388a34;}</style></defs><title>continue</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.334,8,3.667,16H3V0h.667Z"/></g><g id="iconBg"><path class="icon-vs-action-green" d="M4,1.5v13L12.667,8,4,1.5Z"/></g></svg>
<svg width="14" height="14" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#3bb44a;}</style></defs><title>run</title><path class="cls-1" d="M3.24,0,14.61,8,3.24,16Zm2,12.07L11.13,8,5.24,3.88Z"/><path class="cls-1" d="M3.74,1l10,7-10,7Zm1,1.92V13.07L12,8Z"/></svg>

До

Ширина:  |  Высота:  |  Размер: 506 B

После

Ширина:  |  Высота:  |  Размер: 329 B

Просмотреть файл

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-green{fill:#89d185;}</style></defs><title>continue</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.334,8,3.667,16H3V0h.667Z"/></g><g id="iconBg"><path class="icon-vs-action-green" d="M4,1.5v13L12.667,8,4,1.5Z"/></g></svg>

До

Ширина:  |  Высота:  |  Размер: 506 B

Просмотреть файл

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-red{fill:#a1260d;}</style></defs><title>stop</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,2V14H2V2Z"/></g><g id="iconBg"><path class="icon-vs-action-red" d="M13,3V13H3V3Z"/></g></svg>
<svg width="14" height="14" id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d02e00;}</style></defs><title>stop</title><path class="cls-1" d="M.5,15.3V.3h15v15Zm13-2V2.3H2.5v11Z"/><path class="cls-1" d="M1,.8H15v14H1Zm13,13V1.8H2v12Z"/></svg>

До

Ширина:  |  Высота:  |  Размер: 470 B

После

Ширина:  |  Высота:  |  Размер: 307 B

Просмотреть файл

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-red{fill:#f48771;}</style></defs><title>stop</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,2V14H2V2Z"/></g><g id="iconBg"><path class="icon-vs-action-red" d="M13,3V13H3V3Z"/></g></svg>

До

Ширина:  |  Высота:  |  Размер: 470 B

Просмотреть файл

@ -251,9 +251,24 @@
<trans-unit id="msgPromptFirewallRuleCreated">
<source xml:lang="en">Firewall rule successfully created.</source>
</trans-unit>
<trans-unit id="msgChooseQueryHistory">
<source xml:lang="en">Choose Query History</source>
</trans-unit>
<trans-unit id="msgChooseQueryHistoryAction">
<source xml:lang="en">Choose An Action</source>
</trans-unit>
<trans-unit id="msgOpenQueryHistory">
<source xml:lang="en">Open Query History</source>
</trans-unit>
<trans-unit id="msgRunQueryHistory">
<source xml:lang="en">Run Query History</source>
</trans-unit>
<trans-unit id="msgInvalidIpAddress">
<source xml:lang="en">Invalid IP Address </source>
</trans-unit>
<trans-unit id="msgNoQueriesAvailable">
<source xml:lang="en">No Queries Available</source>
</trans-unit>
<trans-unit id="retryLabel">
<source xml:lang="en">Retry</source>
</trans-unit>

Просмотреть файл

@ -151,6 +151,11 @@
{
"id": "objectExplorer",
"name": "%extension.connections%"
},
{
"id": "queryHistory",
"name": "%extension.queryHistory%",
"when": "config.mssql.enableQueryHistoryFeature"
}
]
},
@ -204,6 +209,24 @@
"when": "view == objectExplorer",
"title": "%mssql.addObjectExplorer%",
"group": "navigation"
},
{
"command": "mssql.startQueryHistoryCapture",
"when": "view == queryHistory && config.mssql.enableQueryHistoryFeature && !config.mssql.enableQueryHistoryCapture",
"title": "%mssql.startQueryHistoryCapture%",
"group": "navigation"
},
{
"command": "mssql.pauseQueryHistoryCapture",
"when": "view == queryHistory && config.mssql.enableQueryHistoryFeature && config.mssql.enableQueryHistoryCapture",
"title": "%mssql.pauseQueryHistoryCapture%",
"group": "navigation"
},
{
"command": "mssql.clearAllQueryHistory",
"when": "view == queryHistory",
"title": "%mssql.clearAllQueryHistory%",
"group": "secondary"
}
],
"view/item/context": [
@ -251,6 +274,21 @@
"command": "mssql.scriptAlter",
"when": "view == objectExplorer && viewItem =~ /^(AggregateFunction|PartitionFunction|ScalarValuedFunction|StoredProcedure|TableValuedFunction|View)$/",
"group": "MS_SQL@4"
},
{
"command": "mssql.openQueryHistory",
"when": "view == queryHistory && viewItem == queryHistoryNode",
"group": "MS_SQL@1"
},
{
"command": "mssql.runQueryHistory",
"when": "view == queryHistory && viewItem == queryHistoryNode",
"group": "MS_SQL@2"
},
{
"command": "mssql.deleteQueryHistory",
"when": "view == queryHistory && viewItem == queryHistoryNode",
"group": "MS_SQL@3"
}
],
"commandPalette": [
@ -293,6 +331,14 @@
{
"command": "mssql.toggleSqlCmd",
"when": "editorFocus && editorLangId == 'sql'"
},
{
"command": "mssql.startQueryHistoryCapture",
"when": "config.mssql.enableQueryHistoryFeature && !config.mssql.enableQueryHistoryCapture"
},
{
"command": "mssql.pauseQueryHistoryCapture",
"when": "config.mssql.enableQueryHistoryFeature && config.mssql.enableQueryHistoryCapture"
}
]
},
@ -303,7 +349,7 @@
"category": "MS SQL",
"icon": {
"light": "images/start.svg",
"dark": "images/start_inverse.svg"
"dark": "images/start.svg"
}
},
{
@ -317,7 +363,7 @@
"category": "MS SQL",
"icon": {
"light": "images/stop.svg",
"dark": "images/stop_inverse.svg"
"dark": "images/stop.svg"
}
},
{
@ -418,6 +464,49 @@
"command": "mssql.scriptAlter",
"title": "%mssql.scriptAlter%",
"group": "MS SQL"
},
{
"command": "mssql.openQueryHistory",
"title": "%mssql.openQueryHistory%",
"group": "MS SQL"
},
{
"command": "mssql.runQueryHistory",
"title": "%mssql.runQueryHistory%",
"group": "MS SQL"
},
{
"command": "mssql.deleteQueryHistory",
"title": "%mssql.deleteQueryHistory%",
"group": "MS SQL"
},
{
"command": "mssql.clearAllQueryHistory",
"title": "%mssql.clearAllQueryHistory%",
"group": "MS SQL"
},
{
"command": "mssql.startQueryHistoryCapture",
"title": "%mssql.startQueryHistoryCapture%",
"group": "MS SQL",
"icon": {
"light": "images/start.svg",
"dark": "images/start.svg"
}
},
{
"command": "mssql.pauseQueryHistoryCapture",
"title": "%mssql.pauseQueryHistoryCapture%",
"group": "MS SQL",
"icon": {
"light": "images/stop.svg",
"dark": "images/stop.svg"
}
},
{
"command": "mssql.commandPaletteQueryHistory",
"title": "%mssql.commandPaletteQueryHistory%",
"group": "MS SQL"
}
],
"keybindings": [
@ -826,6 +915,24 @@
"default": false,
"description": "%mssql.persistQueryResultTabs%",
"scope": "window"
},
"mssql.queryHistoryLimit": {
"type": "number",
"default": 20,
"description": "%mssql.queryHistoryLimit%",
"scope": "window"
},
"mssql.enableQueryHistoryCapture": {
"type": "boolean",
"default": true,
"description": "%mssql.enableQueryHistoryCapture%",
"scope": "window"
},
"mssql.enableQueryHistoryFeature": {
"type": "boolean",
"default": true,
"description" : "%mssql.enableQueryHistoryFeature%",
"scope": "window"
}
}
}

Просмотреть файл

@ -8,9 +8,18 @@
"mssql.scriptDelete":"Script as Drop",
"mssql.scriptExecute":"Script as Execute",
"mssql.scriptAlter":"Script as Alter",
"mssql.openQueryHistory":"Open Query",
"mssql.runQueryHistory":"Run Query",
"mssql.deleteQueryHistory":"Delete",
"mssql.clearAllQueryHistory":"Clear All Query History",
"mssql.enableQueryHistoryCapture":"Enable Query History Capture",
"mssql.startQueryHistoryCapture":"Start Query History Capture",
"mssql.pauseQueryHistoryCapture":"Pause Query History Capture",
"mssql.commandPaletteQueryHistory":"Open Query History in Command Palette",
"mssql.removeObjectExplorerNode":"Remove",
"mssql.refreshObjectExplorerNode":"Refresh",
"extension.connections":"Connections",
"extension.queryHistory":"Query History",
"mssql.connect":"Connect",
"mssql.disconnect":"Disconnect",
"mssql.manageProfiles":"Manage Connection Profiles",
@ -79,6 +88,8 @@
"mssql.intelliSense.enableErrorChecking":"Should IntelliSense error checking be enabled",
"mssql.intelliSense.enableSuggestions":"Should IntelliSense suggestions be enabled",
"mssql.intelliSense.enableQuickInfo":"Should IntelliSense quick info be enabled",
"mssql.enableQueryHistoryFeature":"Should Query History feature be enabled",
"mssql.intelliSense.lowerCaseSuggestions":"Should IntelliSense suggestions be lowercase",
"mssql.persistQueryResultTabs":"Should query result selections and scroll positions be saved when switching tabs (may impact performance)"
"mssql.persistQueryResultTabs":"Should query result selections and scroll positions be saved when switching tabs (may impact performance)",
"mssql.queryHistoryLimit":"Number of query history entries to show in the Query History view"
}

Просмотреть файл

@ -9,6 +9,8 @@ export const extensionName = 'mssql';
export const extensionConfigSectionName = 'mssql';
export const mssqlProviderName = 'MSSQL';
export const noneProviderName = 'None';
export const objectExplorerId = 'objectExplorer';
export const queryHistory = 'queryHistory';
export const connectionApplicationName = 'vscode-mssql';
export const outputChannelName = 'MSSQL';
export const connectionConfigFilename = 'settings.json';
@ -24,6 +26,14 @@ export const cmdChooseDatabase = 'mssql.chooseDatabase';
export const cmdChooseLanguageFlavor = 'mssql.chooseLanguageFlavor';
export const cmdShowReleaseNotes = 'mssql.showReleaseNotes';
export const cmdShowGettingStarted = 'mssql.showGettingStarted';
export const cmdRefreshQueryHistory = 'mssql.refreshQueryHistory';
export const cmdClearAllQueryHistory = 'mssql.clearAllQueryHistory';
export const cmdDeleteQueryHistory = 'mssql.deleteQueryHistory';
export const cmdOpenQueryHistory = 'mssql.openQueryHistory';
export const cmdRunQueryHistory = 'mssql.runQueryHistory';
export const cmdStartQueryHistory = 'mssql.startQueryHistoryCapture';
export const cmdPauseQueryHistory = 'mssql.pauseQueryHistoryCapture';
export const cmdCommandPaletteQueryHistory = 'mssql.commandPaletteQueryHistory';
export const cmdNewQuery = 'mssql.newQuery';
export const cmdManageConnectionProfiles = 'mssql.manageProfiles';
export const cmdRebuildIntelliSenseCache = 'mssql.rebuildIntelliSenseCache';
@ -107,6 +117,9 @@ export const sqlToolsServiceDownloadUrlConfigKey = 'downloadUrl';
export const extConfigResultFontFamily = 'resultsFontFamily';
export const configApplyLocalization = 'applyLocalization';
export const configPersistQueryResultTabs = 'persistQueryResultTabs';
export const configQueryHistoryLimit = 'queryHistoryLimit';
export const configEnableQueryHistoryCapture = 'enableQueryHistoryCapture';
export const configEnableQueryHistoryFeature = 'enableQueryHistoryFeature';
// ToolsService Constants
export const serviceInstallingTo = 'Installing SQL tools service to';

Просмотреть файл

@ -30,6 +30,8 @@ import { Deferred } from '../protocol';
import { ConnectTreeNode } from '../objectExplorer/connectTreeNode';
import { ObjectExplorerUtils } from '../objectExplorer/objectExplorerUtils';
import { ScriptOperation } from '../models/contracts/scripting/scriptingRequest';
import { QueryHistoryProvider } from '../queryHistory/queryHistoryProvider';
import { QueryHistoryNode } from '../queryHistory/queryHistoryNode';
/**
* The main controller class that initializes the extension
@ -49,7 +51,9 @@ export default class MainController implements vscode.Disposable {
private _lastOpenedTimer: Utils.Timer;
private _untitledSqlDocumentService: UntitledSqlDocumentService;
private _objectExplorerProvider: ObjectExplorerProvider;
private _queryHistoryProvider: QueryHistoryProvider;
private _scriptingService: ScriptingService;
private _queryHistoryRegistered: boolean = false;
/**
* The main controller constructor
@ -63,7 +67,6 @@ export default class MainController implements vscode.Disposable {
this._connectionMgr = connectionManager;
}
this._vscodeWrapper = vscodeWrapper || new VscodeWrapper();
this._untitledSqlDocumentService = new UntitledSqlDocumentService(this._vscodeWrapper);
}
@ -139,116 +142,9 @@ export default class MainController implements vscode.Disposable {
this.registerCommand(Constants.cmdToggleSqlCmd);
this._event.on(Constants.cmdToggleSqlCmd, async () => { await self.onToggleSqlCmd(); });
// Register the object explorer tree provider
this._objectExplorerProvider = new ObjectExplorerProvider(this._connectionMgr);
this._context.subscriptions.push(
vscode.window.registerTreeDataProvider('objectExplorer', this._objectExplorerProvider)
);
this.initializeObjectExplorer();
// Add Object Explorer Node
this.registerCommand(Constants.cmdAddObjectExplorer);
this._event.on(Constants.cmdAddObjectExplorer, async () => {
if (!self._objectExplorerProvider.objectExplorerExists) {
self._objectExplorerProvider.objectExplorerExists = true;
}
await self.createObjectExplorerSession();
});
// Object Explorer New Query
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdObjectExplorerNewQuery, async (treeNodeInfo: TreeNodeInfo) => {
const connectionCredentials = Object.assign({}, treeNodeInfo.connectionCredentials);
const databaseName = ObjectExplorerUtils.getDatabaseName(treeNodeInfo);
if (databaseName !== connectionCredentials.database &&
databaseName !== LocalizedConstants.defaultDatabaseLabel) {
connectionCredentials.database = databaseName;
} else if (databaseName === LocalizedConstants.defaultDatabaseLabel) {
connectionCredentials.database = '';
}
treeNodeInfo.connectionCredentials = connectionCredentials;
await self.onNewQuery(treeNodeInfo);
}));
// Remove Object Explorer Node
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdRemoveObjectExplorerNode, async (treeNodeInfo: TreeNodeInfo) => {
await this._objectExplorerProvider.removeObjectExplorerNode(treeNodeInfo);
let profile = <IConnectionProfile>treeNodeInfo.connectionCredentials;
await this._connectionMgr.connectionStore.removeProfile(profile, false);
return this._objectExplorerProvider.refresh(undefined);
}));
// Refresh Object Explorer Node
this.registerCommand(Constants.cmdRefreshObjectExplorerNode);
this._event.on(Constants.cmdRefreshObjectExplorerNode, () => {
return this._objectExplorerProvider.refreshNode(this._objectExplorerProvider.currentNode);
});
// Sign In into Object Explorer Node
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdObjectExplorerNodeSignIn, async (node: AccountSignInTreeNode) => {
let profile = <IConnectionProfile>node.parentNode.connectionCredentials;
profile = await self.connectionManager.connectionUI.promptForRetryCreateProfile(profile);
if (profile) {
node.parentNode.connectionCredentials = <IConnectionCredentials>profile;
self._objectExplorerProvider.updateNode(node.parentNode);
self._objectExplorerProvider.signInNodeServer(node.parentNode);
return self._objectExplorerProvider.refresh(undefined);
}
}));
// Connect to Object Explorer Node
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdConnectObjectExplorerNode, async (node: ConnectTreeNode) => {
self._objectExplorerProvider.currentNode = node.parentNode;
await self.createObjectExplorerSession(node.parentNode.connectionCredentials);
}));
// Disconnect Object Explorer Node
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdDisconnectObjectExplorerNode, async (node: TreeNodeInfo) => {
await this._objectExplorerProvider.removeObjectExplorerNode(node, true);
return this._objectExplorerProvider.refresh(undefined);
}));
// Initiate the scripting service
this._scriptingService = new ScriptingService(this._connectionMgr);
// Script as Select
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptSelect, async (node: TreeNodeInfo) => {
await this.scriptNode(node, ScriptOperation.Select, true);
}));
// Script as Create
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptCreate, async (node: TreeNodeInfo) =>
await this.scriptNode(node, ScriptOperation.Create)));
// Script as Drop
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptDelete, async (node: TreeNodeInfo) =>
await this.scriptNode(node, ScriptOperation.Delete)));
// Script as Execute
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptExecute, async (node: TreeNodeInfo) =>
await this.scriptNode(node, ScriptOperation.Execute)));
// Script as Alter
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptAlter, async (node: TreeNodeInfo) =>
await this.scriptNode(node, ScriptOperation.Alter)));
this.initializeQueryHistory();
// Add handlers for VS Code generated commands
this._vscodeWrapper.onDidCloseTextDocument(async (params) => await this.onDidCloseTextDocument(params));
@ -374,6 +270,204 @@ export default class MainController implements vscode.Disposable {
}
}
/**
* Initializes the Object Explorer commands
*/
private initializeObjectExplorer(): void {
const self = this;
// Register the object explorer tree provider
this._objectExplorerProvider = new ObjectExplorerProvider(this._connectionMgr);
this._context.subscriptions.push(
vscode.window.registerTreeDataProvider('objectExplorer', this._objectExplorerProvider)
);
// Add Object Explorer Node
this.registerCommand(Constants.cmdAddObjectExplorer);
this._event.on(Constants.cmdAddObjectExplorer, async () => {
if (!self._objectExplorerProvider.objectExplorerExists) {
self._objectExplorerProvider.objectExplorerExists = true;
}
await self.createObjectExplorerSession();
});
// Object Explorer New Query
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdObjectExplorerNewQuery, async (treeNodeInfo: TreeNodeInfo) => {
const connectionCredentials = Object.assign({}, treeNodeInfo.connectionCredentials);
const databaseName = ObjectExplorerUtils.getDatabaseName(treeNodeInfo);
if (databaseName !== connectionCredentials.database &&
databaseName !== LocalizedConstants.defaultDatabaseLabel) {
connectionCredentials.database = databaseName;
} else if (databaseName === LocalizedConstants.defaultDatabaseLabel) {
connectionCredentials.database = '';
}
treeNodeInfo.connectionCredentials = connectionCredentials;
await self.onNewQuery(treeNodeInfo);
}));
// Remove Object Explorer Node
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdRemoveObjectExplorerNode, async (treeNodeInfo: TreeNodeInfo) => {
await this._objectExplorerProvider.removeObjectExplorerNode(treeNodeInfo);
let profile = <IConnectionProfile>treeNodeInfo.connectionCredentials;
await this._connectionMgr.connectionStore.removeProfile(profile, false);
return this._objectExplorerProvider.refresh(undefined);
}));
// Refresh Object Explorer Node
this.registerCommand(Constants.cmdRefreshObjectExplorerNode);
this._event.on(Constants.cmdRefreshObjectExplorerNode, () => {
return this._objectExplorerProvider.refreshNode(this._objectExplorerProvider.currentNode);
});
// Sign In into Object Explorer Node
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdObjectExplorerNodeSignIn, async (node: AccountSignInTreeNode) => {
let profile = <IConnectionProfile>node.parentNode.connectionCredentials;
profile = await self.connectionManager.connectionUI.promptForRetryCreateProfile(profile);
if (profile) {
node.parentNode.connectionCredentials = <IConnectionCredentials>profile;
self._objectExplorerProvider.updateNode(node.parentNode);
self._objectExplorerProvider.signInNodeServer(node.parentNode);
return self._objectExplorerProvider.refresh(undefined);
}
}));
// Connect to Object Explorer Node
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdConnectObjectExplorerNode, async (node: ConnectTreeNode) => {
self._objectExplorerProvider.currentNode = node.parentNode;
await self.createObjectExplorerSession(node.parentNode.connectionCredentials);
}));
// Disconnect Object Explorer Node
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdDisconnectObjectExplorerNode, async (node: TreeNodeInfo) => {
await this._objectExplorerProvider.removeObjectExplorerNode(node, true);
return this._objectExplorerProvider.refresh(undefined);
}));
// Initiate the scripting service
this._scriptingService = new ScriptingService(this._connectionMgr);
// Script as Select
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptSelect, async (node: TreeNodeInfo) => {
await this.scriptNode(node, ScriptOperation.Select, true);
}));
// Script as Create
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptCreate, async (node: TreeNodeInfo) =>
await this.scriptNode(node, ScriptOperation.Create)));
// Script as Drop
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptDelete, async (node: TreeNodeInfo) =>
await this.scriptNode(node, ScriptOperation.Delete)));
// Script as Execute
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptExecute, async (node: TreeNodeInfo) =>
await this.scriptNode(node, ScriptOperation.Execute)));
// Script as Alter
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdScriptAlter, async (node: TreeNodeInfo) =>
await this.scriptNode(node, ScriptOperation.Alter)));
}
/**
* Initializes the Query History commands
*/
private initializeQueryHistory(): void {
let config = this._vscodeWrapper.getConfiguration(Constants.extensionConfigSectionName);
let queryHistoryFeature = config.get(Constants.configEnableQueryHistoryFeature);
// If the query history feature is enabled
if (queryHistoryFeature && !this._queryHistoryRegistered) {
// Register the query history tree provider
this._queryHistoryProvider = new QueryHistoryProvider(this._connectionMgr, this._outputContentProvider,
this._vscodeWrapper, this._untitledSqlDocumentService, this._statusview, this._prompter);
this._context.subscriptions.push(
vscode.window.registerTreeDataProvider('queryHistory', this._queryHistoryProvider)
);
// Command to refresh Query History
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdRefreshQueryHistory, (ownerUri: string, hasError: boolean) => {
config = this._vscodeWrapper.getConfiguration(Constants.extensionConfigSectionName);
let queryHistoryFeatureEnabled = config.get(Constants.configEnableQueryHistoryFeature);
let queryHistoryCaptureEnabled = config.get(Constants.configEnableQueryHistoryCapture);
if (queryHistoryFeatureEnabled && queryHistoryCaptureEnabled) {
const timeStamp = new Date();
this._queryHistoryProvider.refresh(ownerUri, timeStamp, hasError);
}
}));
// Command to enable clear all entries in Query History
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdClearAllQueryHistory, () => {
this._queryHistoryProvider.clearAll();
}));
// Command to enable delete an entry in Query History
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdDeleteQueryHistory, (node: QueryHistoryNode) => {
this._queryHistoryProvider.deleteQueryHistoryEntry(node);
}));
// Command to enable open a query in Query History
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdOpenQueryHistory, async (node: QueryHistoryNode) => {
await this._queryHistoryProvider.openQueryHistoryEntry(node);
}));
// Command to enable run a query in Query History
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdRunQueryHistory, async (node: QueryHistoryNode) => {
await this._queryHistoryProvider.openQueryHistoryEntry(node, true);
}));
// Command to start the query history capture
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdStartQueryHistory, async (node: QueryHistoryNode) => {
await this._queryHistoryProvider.startQueryHistoryCapture();
}));
// Command to pause the query history capture
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdPauseQueryHistory, async (node: QueryHistoryNode) => {
await this._queryHistoryProvider.pauseQueryHistoryCapture();
}));
// Command to open the query history experience in the command palette
this._context.subscriptions.push(
vscode.commands.registerCommand(
Constants.cmdCommandPaletteQueryHistory, async () => {
await this._queryHistoryProvider.showQueryHistoryCommandPalette();
}));
this._queryHistoryRegistered = true;
}
}
/**
* Handles the command to enable SQLCMD mode
@ -892,12 +986,24 @@ export default class MainController implements vscode.Disposable {
this._lastSavedUri = savedDocumentUri;
}
private async onChangeQueryHistoryConfig(): Promise<void> {
let queryHistoryFeatureEnabled = this._vscodeWrapper.getConfiguration(Constants.extensionConfigSectionName)
.get(Constants.configEnableQueryHistoryFeature);
if (queryHistoryFeatureEnabled) {
this.initializeQueryHistory();
}
}
/**
* Called by VS Code when user settings are changed
* @param ConfigurationChangeEvent event that is fired when config is changed
*/
public async onDidChangeConfiguration(e: vscode.ConfigurationChangeEvent): Promise<void> {
if (e.affectsConfiguration(Constants.extensionName)) {
// Query History settings change
await this.onChangeQueryHistoryConfig();
// Connections change
let needsRefresh = false;
// user connections is a super set of object explorer connections
let userConnections: any[] = this._vscodeWrapper.getConfiguration(Constants.extensionName).get(Constants.connectionsArrayName);

Просмотреть файл

@ -35,6 +35,7 @@ export interface IResultSet {
columns: string[];
totalNumberOfRows: number;
}
/*
* Query Runner class which handles running a query, reports the results to the content manager,
* and handles getting more rows from the service layer and disposing when the content is closed.
@ -49,6 +50,7 @@ export default class QueryRunner {
private _isSqlCmd: boolean = false;
public eventEmitter: EventEmitter = new EventEmitter();
private _uriToQueryPromiseMap = new Map<string, Deferred<boolean>>();
private _uriToQueryStringMap = new Map<string, string>();
// CONSTRUCTOR /////////////////////////////////////////////////////////
@ -160,6 +162,20 @@ export default class QueryRunner {
querySelection: selection
};
const doc = await this._vscodeWrapper.openTextDocument(this._vscodeWrapper.parseUri(this._ownerUri));
let queryString: string;
if (selection) {
let range = this._vscodeWrapper.range(
this._vscodeWrapper.position(selection.startLine, selection.startColumn),
this._vscodeWrapper.position(selection.endLine, selection.endColumn));
queryString = doc.getText(range);
} else {
queryString = doc.getText();
}
// Set the query string for the uri
this._uriToQueryStringMap.set(this._ownerUri, queryString);
// Send the request to execute the query
if (promise) {
this._uriToQueryPromiseMap.set(this._ownerUri, promise);
@ -216,7 +232,8 @@ export default class QueryRunner {
this._uriToQueryPromiseMap.delete(result.ownerUri);
}
this._statusView.executedQuery(result.ownerUri);
this.eventEmitter.emit('complete', Utils.parseNumAsTimeString(this._totalElapsedMilliseconds));
let hasError = this._batchSets.some(batch => batch.hasError === true);
this.eventEmitter.emit('complete', Utils.parseNumAsTimeString(this._totalElapsedMilliseconds), hasError);
}
public handleBatchStart(result: QueryExecuteBatchNotificationParams): void {
@ -576,6 +593,13 @@ export default class QueryRunner {
}
}
public getQueryString(uri: string): string {
if (this._uriToQueryStringMap.has(uri)) {
return this._uriToQueryStringMap.get(uri);
}
return undefined;
}
public resetHasCompleted(): void {
this._hasCompleted = false;
}

Просмотреть файл

@ -345,6 +345,13 @@ export default class VscodeWrapper {
return this.getConfiguration(extensionName).update(resource, value, vscode.ConfigurationTarget.Global);
}
/**
* Set a context for contributing command actions
*/
public async setContext(contextSection: string, value: any): Promise<void> {
await this.executeCommand('setContext', contextSection, value);
}
/*
* Called when there's a change in the extensions
*/

Просмотреть файл

@ -215,7 +215,8 @@ export class SqlOutputContentProvider {
queryRunner.eventEmitter.on('message', (message) => {
this._panels.get(uri).proxy.sendEvent('message', message);
});
queryRunner.eventEmitter.on('complete', (totalMilliseconds) => {
queryRunner.eventEmitter.on('complete', (totalMilliseconds, hasError) => {
this._vscodeWrapper.executeCommand(Constants.cmdRefreshQueryHistory, uri, hasError);
this._panels.get(uri).proxy.sendEvent('complete', totalMilliseconds);
});
this._queryResultsMap.set(uri, new QueryRunnerState(queryRunner));

Просмотреть файл

@ -12,7 +12,7 @@ import * as findRemoveSync from 'find-remove';
import vscode = require('vscode');
import Constants = require('../constants/constants');
import { AzureSignInQuickPickItem, IConnectionCredentials, IConnectionProfile, AuthenticationTypes } from './interfaces';
import {ExtensionContext} from 'vscode';
import { ExtensionContext } from 'vscode';
import LocalizedConstants = require('../constants/localizedConstants');
import fs = require('fs');
@ -59,14 +59,14 @@ export function generateGuid(): string {
/* tslint:disable:no-bitwise */
for (let a: number = 0; a < 4; a++) {
tmp = (4294967296 * Math.random()) | 0;
oct += hexValues[tmp & 0xF] +
hexValues[tmp >> 4 & 0xF] +
hexValues[tmp >> 8 & 0xF] +
hexValues[tmp >> 12 & 0xF] +
hexValues[tmp >> 16 & 0xF] +
hexValues[tmp >> 20 & 0xF] +
hexValues[tmp >> 24 & 0xF] +
hexValues[tmp >> 28 & 0xF];
oct += hexValues[tmp & 0xF] +
hexValues[tmp >> 4 & 0xF] +
hexValues[tmp >> 8 & 0xF] +
hexValues[tmp >> 12 & 0xF] +
hexValues[tmp >> 16 & 0xF] +
hexValues[tmp >> 20 & 0xF] +
hexValues[tmp >> 24 & 0xF] +
hexValues[tmp >> 28 & 0xF];
}
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
@ -148,17 +148,17 @@ export function logDebug(msg: any): void {
// Helper to show an info message
export function showInfoMsg(msg: string): void {
vscode.window.showInformationMessage(Constants.extensionName + ': ' + msg );
vscode.window.showInformationMessage(Constants.extensionName + ': ' + msg);
}
// Helper to show an warn message
export function showWarnMsg(msg: string): void {
vscode.window.showWarningMessage(Constants.extensionName + ': ' + msg );
vscode.window.showWarningMessage(Constants.extensionName + ': ' + msg);
}
// Helper to show an error message
export function showErrorMsg(msg: string): void {
vscode.window.showErrorMessage(Constants.extensionName + ': ' + msg );
vscode.window.showErrorMessage(Constants.extensionName + ': ' + msg);
}
export function isEmpty(str: any): boolean {
@ -264,8 +264,8 @@ export function isSameConnection(conn: IConnectionCredentials, expectedConn: ICo
&& isSameDatabase(expectedConn.database, conn.database)
&& isSameAuthenticationType(expectedConn.authenticationType, conn.authenticationType)
&& (conn.authenticationType === Constants.sqlAuthentication ?
conn.user === expectedConn.user :
isEmpty(conn.user) === isEmpty(expectedConn.user))
conn.user === expectedConn.user :
isEmpty(conn.user) === isEmpty(expectedConn.user))
&& (<IConnectionProfile>conn).savePassword ===
(<IConnectionProfile>expectedConn).savePassword;
}
@ -274,13 +274,13 @@ export function isSameConnection(conn: IConnectionCredentials, expectedConn: ICo
* Check if a file exists on disk
*/
export function isFileExisting(filePath: string): boolean {
try {
fs.statSync(filePath);
return true;
} catch (err) {
return false;
}
try {
fs.statSync(filePath);
return true;
} catch (err) {
return false;
}
}
// One-time use timer for performance testing
@ -298,7 +298,7 @@ export class Timer {
return -1;
} else if (!this._endTime) {
let endTime = process.hrtime(<any>this._startTime);
return endTime[0] * 1000 + endTime[1] / 1000000;
return endTime[0] * 1000 + endTime[1] / 1000000;
} else {
return this._endTime[0] * 1000 + this._endTime[1] / 1000000;
}
@ -385,53 +385,51 @@ function getConfiguration(): vscode.WorkspaceConfiguration {
}
export function getConfigTracingLevel(): string {
let config = getConfiguration();
if (config) {
return config.get(configTracingLevel);
}
else {
return undefined;
}
let config = getConfiguration();
if (config) {
return config.get(configTracingLevel);
} else {
return undefined;
}
}
export function getConfigLogFilesRemovalLimit(): number {
let config = getConfiguration();
if (config) {
return Number((config.get(configLogFilesRemovalLimit, 0).toFixed(0)));
}
else {
return undefined;
}
let config = getConfiguration();
if (config) {
return Number((config.get(configLogFilesRemovalLimit, 0).toFixed(0)));
}
else {
return undefined;
}
}
export function getConfigLogRetentionSeconds(): number {
let config = getConfiguration();
if (config) {
return Number((config.get(configLogRetentionMinutes, 0) * 60).toFixed(0));
}
else {
return undefined;
}
let config = getConfiguration();
if (config) {
return Number((config.get(configLogRetentionMinutes, 0) * 60).toFixed(0));
} else {
return undefined;
}
}
export function removeOldLogFiles(logPath: string, prefix: string): JSON {
return findRemoveSync(logPath, { age: { seconds: getConfigLogRetentionSeconds() }, limit: getConfigLogFilesRemovalLimit() });
return findRemoveSync(logPath, { age: { seconds: getConfigLogRetentionSeconds() }, limit: getConfigLogFilesRemovalLimit() });
}
export function getCommonLaunchArgsAndCleanupOldLogFiles(logPath: string, fileName: string, executablePath: string): string[] {
let launchArgs = [];
launchArgs.push('--log-file');
let logFile = path.join(logPath, fileName);
launchArgs.push(logFile);
let launchArgs = [];
launchArgs.push('--log-file');
let logFile = path.join(logPath, fileName);
launchArgs.push(logFile);
console.log(`logFile for ${path.basename(executablePath)} is ${logFile}`);
console.log(`This process (ui Extenstion Host) is pid: ${process.pid}`);
// Delete old log files
let deletedLogFiles = removeOldLogFiles(logPath, fileName);
console.log(`Old log files deletion report: ${JSON.stringify(deletedLogFiles)}`);
launchArgs.push('--tracing-level');
launchArgs.push(getConfigTracingLevel());
return launchArgs;
console.log(`logFile for ${path.basename(executablePath)} is ${logFile}`);
console.log(`This process (ui Extenstion Host) is pid: ${process.pid}`);
// Delete old log files
let deletedLogFiles = removeOldLogFiles(logPath, fileName);
console.log(`Old log files deletion report: ${JSON.stringify(deletedLogFiles)}`);
launchArgs.push('--tracing-level');
launchArgs.push(getConfigTracingLevel());
return launchArgs;
}
/**
@ -454,4 +452,20 @@ export function getSignInQuickPickItems(): AzureSignInQuickPickItem[] {
command: Constants.cmdAzureSignInToCloud
};
return [signInItem, signInWithDeviceCode, signInAzureCloud];
}
}
/**
* Limits the size of a string with ellipses in the middle
*/
export function limitStringSize(input: string, forCommandPalette: boolean = false): string {
if (!forCommandPalette) {
if (input.length > 45) {
return `${input.substr(0, 20)}...${input.substr(input.length - 20, input.length)}`;
}
} else {
if (input.length > 100) {
return `${input.substr(0, 45)}...${input.substr(input.length - 45, input.length)}`;
}
}
return input;
}

Просмотреть файл

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d02e00;}</style></defs><title>globalerror_red</title><path class="cls-1" d="M8,0a7.92,7.92,0,0,1,4,1.09A8.15,8.15,0,0,1,14.91,4a8,8,0,0,1,.81,1.91,8,8,0,0,1-.81,6.16A8.15,8.15,0,0,1,12,14.92a8,8,0,0,1-8.07,0,8.15,8.15,0,0,1-2.87-2.87A8,8,0,0,1,1.09,4,8.15,8.15,0,0,1,4,1.11,7.92,7.92,0,0,1,8,0ZM8,15a6.88,6.88,0,0,0,1.86-.25,7,7,0,0,0,4.89-4.89,7.07,7.07,0,0,0,0-3.73A7,7,0,0,0,9.86,1.27a7.07,7.07,0,0,0-3.73,0A7,7,0,0,0,1.25,6.15a7.07,7.07,0,0,0,0,3.73,7,7,0,0,0,4.89,4.89A6.88,6.88,0,0,0,8,15Zm3.46-9.76L8.71,8l2.75,2.76-.7.7L8,8.73,5.24,11.48l-.7-.7L7.29,8,4.54,5.26l.7-.7L8,7.31l2.76-2.75Z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 721 B

Просмотреть файл

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#3bb44a;}</style></defs><title>success_16x16</title><path class="cls-1" d="M16,3.16,5.48,13.69,0,8.2l.89-.89,4.6,4.59,9.63-9.62Z"/></svg>

После

Ширина:  |  Высота:  |  Размер: 255 B

Просмотреть файл

@ -0,0 +1,79 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as vscode from 'vscode';
import * as path from 'path';
import * as LocalizedConstants from '../constants/localizedConstants';
import { queryHistory } from '../constants/constants';
/**
* Empty Node shown when no queries are available
*/
export class EmptyHistoryNode extends vscode.TreeItem {
private static readonly contextValue = 'emptyHistoryNode';
constructor() {
super(LocalizedConstants.msgNoQueriesAvailable, vscode.TreeItemCollapsibleState.None);
this.contextValue = EmptyHistoryNode.contextValue;
}
}
/**
* Query history node
*/
export class QueryHistoryNode extends vscode.TreeItem {
private static readonly contextValue = 'queryHistoryNode';
private readonly iconsPath: string = path.join(__dirname, 'icons');
private readonly successIcon: string = path.join(this.iconsPath, 'status_success.svg');
private readonly failureIcon: string = path.join(this.iconsPath, 'status_error.svg');
private _ownerUri: string;
private _timeStamp: Date;
private _isSuccess: boolean;
private _queryString: string;
private _connectionLabel: string;
constructor(
label: string,
tooltip: string,
queryString: string,
ownerUri: string,
timeStamp: Date,
connectionLabel: string,
isSuccess: boolean
) {
super(label, vscode.TreeItemCollapsibleState.None);
this._queryString = queryString;
this._ownerUri = ownerUri;
this._timeStamp = timeStamp;
this._isSuccess = isSuccess;
this._connectionLabel = connectionLabel;
this.iconPath = this._isSuccess ? this.successIcon : this.failureIcon;
this.tooltip = tooltip;
this.contextValue = QueryHistoryNode.contextValue;
}
/** Getters */
public get historyNodeLabel(): string {
return this.label;
}
public get ownerUri(): string {
return this._ownerUri;
}
public get timeStamp(): Date {
return this._timeStamp;
}
public get queryString(): string {
return this._queryString;
}
public get connectionLabel(): string {
return this._connectionLabel;
}
}

Просмотреть файл

@ -0,0 +1,196 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */
import * as vscode from 'vscode';
import * as path from 'path';
import * as os from 'os';
import * as Utils from '../models/utils';
import ConnectionManager from '../controllers/connectionManager';
import { SqlOutputContentProvider } from '../models/sqlOutputContentProvider';
import { QueryHistoryNode, EmptyHistoryNode } from './queryHistoryNode';
import VscodeWrapper from '../controllers/vscodeWrapper';
import Constants = require('../constants/constants');
import UntitledSqlDocumentService from '../controllers/untitledSqlDocumentService';
import { Deferred } from '../protocol';
import StatusView from '../views/statusView';
import { IConnectionProfile } from '../models/interfaces';
import { IPrompter } from '../prompts/question';
import { QueryHistoryUI, QueryHistoryAction } from '../views/queryHistoryUI';
export class QueryHistoryProvider implements vscode.TreeDataProvider<any> {
private _onDidChangeTreeData: vscode.EventEmitter<any | undefined> = new vscode.EventEmitter<any | undefined>();
readonly onDidChangeTreeData: vscode.Event<any | undefined> = this._onDidChangeTreeData.event;
private _queryHistoryNodes: vscode.TreeItem[] = [new EmptyHistoryNode()]
private _queryHistoryLimit: number;
private _queryHistoryUI: QueryHistoryUI;
constructor(
private _connectionManager: ConnectionManager,
private _outputContentProvider: SqlOutputContentProvider,
private _vscodeWrapper: VscodeWrapper,
private _untitledSqlDocumentService: UntitledSqlDocumentService,
private _statusView: StatusView,
private _prompter: IPrompter
) {
const config = this._vscodeWrapper.getConfiguration(Constants.extensionConfigSectionName);
this._queryHistoryLimit = config.get(Constants.configQueryHistoryLimit);
this._queryHistoryUI = new QueryHistoryUI(this._prompter, this._vscodeWrapper);
}
clearAll(): void {
this._queryHistoryNodes = [new EmptyHistoryNode()];
this._onDidChangeTreeData.fire();
}
refresh(ownerUri: string, timeStamp: Date, hasError): void {
const timeStampString = timeStamp.toLocaleString();
const historyNodeLabel = this.createHistoryNodeLabel(ownerUri);
const tooltip = this.createHistoryNodeTooltip(ownerUri, timeStampString);
const queryString = this.getQueryString(ownerUri);
const connectionLabel = this.getConnectionLabel(ownerUri);
const node = new QueryHistoryNode(historyNodeLabel, tooltip, queryString,
ownerUri, timeStamp, connectionLabel, !hasError);
if (this._queryHistoryNodes.length === 1) {
if (this._queryHistoryNodes[0] instanceof EmptyHistoryNode) {
this._queryHistoryNodes = [];
}
}
this._queryHistoryNodes.push(node);
// sort the query history sorted by timestamp
this._queryHistoryNodes.sort((a, b) => {
return (b as QueryHistoryNode).timeStamp.getTime() -
(a as QueryHistoryNode).timeStamp.getTime();
});
// Push out the first listing if it crosses limit to maintain
// an LRU order
if (this._queryHistoryNodes.length > this._queryHistoryLimit) {
this._queryHistoryNodes.shift();
}
this._onDidChangeTreeData.fire();
}
getTreeItem(node: QueryHistoryNode): QueryHistoryNode {
return node;
}
getChildren(element?: any): vscode.TreeItem[] {
if (this._queryHistoryNodes.length === 0) {
this._queryHistoryNodes.push(new EmptyHistoryNode());
}
return this._queryHistoryNodes;
}
/**
* Shows the Query History List on the command palette
*/
public async showQueryHistoryCommandPalette(): Promise<void | undefined> {
const options = this._queryHistoryNodes.map(node => this._queryHistoryUI.convertToQuickPickItem(node));
let queryHistoryQuickPickItem = await this._queryHistoryUI.showQueryHistoryCommandPalette(options);
if (queryHistoryQuickPickItem) {
await this.openQueryHistoryEntry(queryHistoryQuickPickItem.node, queryHistoryQuickPickItem.action ===
QueryHistoryAction.RunQueryHistoryAction);
}
return undefined;
}
/**
* Starts the history capture by changing the setting
* and changes context for menu actions
*/
public async startQueryHistoryCapture(): Promise<void> {
await this._vscodeWrapper.setConfiguration(Constants.extensionConfigSectionName,
Constants.configEnableQueryHistoryCapture, true);
}
/**
* Pauses the history capture by changing the setting
* and changes context for menu actions
*/
public async pauseQueryHistoryCapture(): Promise<void> {
await this._vscodeWrapper.setConfiguration(Constants.extensionConfigSectionName,
Constants.configEnableQueryHistoryCapture, false);
}
/**
* Opens a query history listing in a new query window
*/
public async openQueryHistoryEntry(node: QueryHistoryNode, isExecute: boolean = false): Promise<void> {
const editor = await this._untitledSqlDocumentService.newQuery(node.queryString);
let uri = editor.document.uri.toString(true);
let title = path.basename(editor.document.fileName);
const queryUriPromise = new Deferred<boolean>();
let credentials = this._connectionManager.getConnectionInfo(node.ownerUri).credentials;
await this._connectionManager.connect(uri, credentials, queryUriPromise);
await queryUriPromise;
this._statusView.languageFlavorChanged(uri, Constants.mssqlProviderName);
this._statusView.sqlCmdModeChanged(uri, false);
if (isExecute) {
const queryPromise = new Deferred<boolean>();
await this._outputContentProvider.runQuery(this._statusView, uri, undefined, title, queryPromise);
await queryPromise;
await this._connectionManager.connectionStore.removeRecentlyUsed(<IConnectionProfile>credentials);
}
}
/**
* Deletes a query history entry for a URI
*/
public deleteQueryHistoryEntry(node: QueryHistoryNode): void {
let index = this._queryHistoryNodes.findIndex(n => {
let historyNode = n as QueryHistoryNode;
return historyNode === node;
});
this._queryHistoryNodes.splice(index, 1);
this._onDidChangeTreeData.fire();
}
/**
* Getters
*/
public get queryHistoryNodes(): vscode.TreeItem[] {
return this._queryHistoryNodes;
}
/**
* Creates the node label for a query history node
*/
private createHistoryNodeLabel(ownerUri: string): string {
const queryString = Utils.limitStringSize(this.getQueryString(ownerUri)).trim();
const connectionLabel = Utils.limitStringSize(this.getConnectionLabel(ownerUri)).trim();
return `${queryString} : ${connectionLabel}`;
}
/**
* Gets the selected text for the corresponding query history listing
*/
private getQueryString(ownerUri: string): string {
const queryRunner = this._outputContentProvider.getQueryRunner(ownerUri);
return queryRunner.getQueryString(ownerUri);
}
/**
* Creates a connection label based on credentials
*/
private getConnectionLabel(ownerUri: string): string {
const connInfo = this._connectionManager.getConnectionInfo(ownerUri);
const credentials = connInfo.credentials;
let connString = `(${credentials.server}|${credentials.database})`;
if (credentials.authenticationType === Constants.sqlAuthentication) {
connString = `${connString} : ${credentials.user}`;
}
return connString;
}
/**
* Creates a detailed tool tip when a node is hovered
*/
private createHistoryNodeTooltip(ownerUri: string, timeStamp: string): string {
const queryString = this.getQueryString(ownerUri);
const connectionLabel = this.getConnectionLabel(ownerUri);
return `${connectionLabel}${os.EOL}${os.EOL}${timeStamp}${os.EOL}${os.EOL}${queryString}`;
}
}

Просмотреть файл

@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as Utils from '../models/utils';
import VscodeWrapper from '../controllers/vscodeWrapper';
import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question';
import { QueryHistoryNode } from '../queryHistory/queryHistoryNode';
import * as LocalizedConstants from '../constants/localizedConstants';
export enum QueryHistoryAction {
OpenQueryHistoryAction = 1,
RunQueryHistoryAction = 2
}
// tslint:disable-next-line: interface-name
export interface QueryHistoryQuickPickItem extends vscode.QuickPickItem {
node: QueryHistoryNode;
action: any;
}
export class QueryHistoryUI {
constructor(
private _prompter: IPrompter,
private _vscodeWrapper: VscodeWrapper
) {}
public convertToQuickPickItem(node: vscode.TreeItem): QueryHistoryQuickPickItem {
let historyNode = node as QueryHistoryNode;
let quickPickItem: QueryHistoryQuickPickItem = {
label: Utils.limitStringSize(historyNode.queryString, true).trim(),
detail: `${historyNode.connectionLabel}, ${historyNode.timeStamp.toLocaleString()}`,
node: historyNode,
action: undefined,
picked: false
};
return quickPickItem;
}
private showQueryHistoryActions(node: QueryHistoryNode): Promise<string | undefined> {
let options = [{ label: LocalizedConstants.msgOpenQueryHistory },
{ label: LocalizedConstants.msgRunQueryHistory }];
let question: IQuestion = {
type: QuestionTypes.expand,
name: 'question',
message: LocalizedConstants.msgChooseQueryHistoryAction,
choices: options
};
return this._prompter.promptSingle(question).then((answer: vscode.QuickPickItem) => {
if (answer) {
return answer.label;
}
return undefined;
});
}
/**
* Shows the Query History List on the command palette
*/
public showQueryHistoryCommandPalette(options: vscode.QuickPickItem[]): Promise<QueryHistoryQuickPickItem | undefined> {
let question: IQuestion = {
type: QuestionTypes.expand,
name: 'question',
message: LocalizedConstants.msgChooseQueryHistory,
choices: options
};
return this._prompter.promptSingle(question).then((answer: QueryHistoryQuickPickItem) => {
if (answer) {
return this.showQueryHistoryActions(answer.node).then((actionAnswer: string) => {
if (actionAnswer === LocalizedConstants.msgOpenQueryHistory) {
answer.action = QueryHistoryAction.OpenQueryHistoryAction;
} else if ( actionAnswer === LocalizedConstants.msgRunQueryHistory) {
answer.action = QueryHistoryAction.RunQueryHistoryAction;
}
return answer;
});
}
return undefined;
});
}
}

Просмотреть файл

@ -72,6 +72,10 @@ suite('Query Runner tests', () => {
// ... Mock up the view and VSCode wrapper to handle requests to update view
testStatusView.setup(x => x.executingQuery(TypeMoq.It.isAnyString()));
testVscodeWrapper.setup( x => x.logToOutputChannel(TypeMoq.It.isAnyString()));
let testDoc: vscode.TextDocument = {
getText() {}
} as any;
testVscodeWrapper.setup( x => x.openTextDocument(TypeMoq.It.isAny())).returns(() => Promise.resolve(testDoc));
// ... Mock up a event emitter to accept a start event (only)
let mockEventEmitter = TypeMoq.Mock.ofType(EventEmitter, TypeMoq.MockBehavior.Loose);
@ -122,6 +126,10 @@ suite('Query Runner tests', () => {
// ... Setup the vs code wrapper to handle output logging and error messages
testVscodeWrapper.setup(x => x.logToOutputChannel(TypeMoq.It.isAnyString()));
testVscodeWrapper.setup(x => x.showErrorMessage(TypeMoq.It.isAnyString()));
let testDoc: vscode.TextDocument = {
getText() {}
} as any;
testVscodeWrapper.setup( x => x.openTextDocument(TypeMoq.It.isAny())).returns(() => Promise.resolve(testDoc));
// ... Setup the event emitter to handle nothing
let testEventEmitter = TypeMoq.Mock.ofType(EventEmitter, TypeMoq.MockBehavior.Strict);
@ -386,7 +394,7 @@ suite('Query Runner tests', () => {
// Setup:
// ... Create a mock for an event emitter that handles complete notifications
let mockEventEmitter = TypeMoq.Mock.ofType(EventEmitter, TypeMoq.MockBehavior.Strict);
mockEventEmitter.setup(x => x.emit('complete', TypeMoq.It.isAnyString()));
mockEventEmitter.setup(x => x.emit('complete', TypeMoq.It.isAnyString(), TypeMoq.It.isAny()));
// ... Setup the VS Code view handlers
testStatusView.setup(x => x.executedQuery(TypeMoq.It.isAny()));
@ -426,7 +434,7 @@ suite('Query Runner tests', () => {
testStatusView.verify(x => x.executedQuery(standardUri), TypeMoq.Times.once());
// ... The event emitter should have gotten a complete event
mockEventEmitter.verify(x => x.emit('complete', TypeMoq.It.isAnyString()), TypeMoq.Times.once());
mockEventEmitter.verify(x => x.emit('complete', TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
// ... The state of the query runner has been updated
assert.equal(queryRunner.batchSets.length, 1);