Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

added new command to create new SQL query document #569

Merged
merged 5 commits into from
Jan 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
"onCommand:extension.manageProfiles",
"onCommand:extension.chooseDatabase",
"onCommand:extension.cancelQuery",
"onCommand:extension.showGettingStarted"
"onCommand:extension.showGettingStarted",
"onCommand:extension.newQuery"
],
"main": "./out/src/extension",
"extensionDependencies": [
Expand Down Expand Up @@ -179,6 +180,11 @@
"command": "extension.showGettingStarted",
"title": "Getting Started Guide",
"category": "MS SQL"
},
{
"command": "extension.newQuery",
"title": "New query",
"category": "MS SQL"
}
],
"keybindings": [
Expand Down
25 changes: 21 additions & 4 deletions src/controllers/mainController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { IPrompter } from '../prompts/question';
import CodeAdapter from '../prompts/adapter';
import Telemetry from '../models/telemetry';
import VscodeWrapper from './vscodeWrapper';
import UntitledSqlDocumentService from './untitledSqlDocumentService';
import { ISelectionData } from './../models/interfaces';
import * as path from 'path';
import fs = require('fs');
Expand All @@ -38,6 +39,7 @@ export default class MainController implements vscode.Disposable {
private _lastSavedTimer: Utils.Timer;
private _lastOpenedUri: string;
private _lastOpenedTimer: Utils.Timer;
private _untitledSqlDocumentService: UntitledSqlDocumentService;

/**
* The main controller constructor
Expand All @@ -50,9 +52,9 @@ export default class MainController implements vscode.Disposable {
if (connectionManager) {
this._connectionMgr = connectionManager;
}
if (vscodeWrapper) {
this._vscodeWrapper = vscodeWrapper;
}
this._vscodeWrapper = vscodeWrapper || new VscodeWrapper();

this._untitledSqlDocumentService = new UntitledSqlDocumentService(this._vscodeWrapper);
}

/**
Expand Down Expand Up @@ -104,8 +106,10 @@ export default class MainController implements vscode.Disposable {
this._event.on(Constants.cmdCancelQuery, () => { self.onCancelQuery(); });
this.registerCommand(Constants.cmdShowGettingStarted);
this._event.on(Constants.cmdShowGettingStarted, () => { self.launchGettingStartedPage(); });
this.registerCommand(Constants.cmdNewQuery);
this._event.on(Constants.cmdNewQuery, () => { self.runAndLogErrors(self.onNewQuery(), 'onNewQuery'); });

this._vscodeWrapper = new VscodeWrapper();
// this._vscodeWrapper = new VscodeWrapper();

// Add handlers for VS Code generated commands
this._vscodeWrapper.onDidCloseTextDocument(params => this.onDidCloseTextDocument(params));
Expand Down Expand Up @@ -306,6 +310,10 @@ export default class MainController implements vscode.Disposable {
this._connectionMgr = connectionManager;
}

public set untitledSqlDocumentService(untitledSqlDocumentService: UntitledSqlDocumentService) {
this._untitledSqlDocumentService = untitledSqlDocumentService;
}


/**
* Verifies the extension is initilized and if not shows an error message
Expand Down Expand Up @@ -360,6 +368,15 @@ export default class MainController implements vscode.Disposable {
opener(Constants.gettingStartedGuideLink);
}

/**
* Opens a new query and creates new connection
*/
public onNewQuery(): Promise<boolean> {
return this._untitledSqlDocumentService.newQuery().then(x => {
return this._connectionMgr.onNewConnection();
});
}

/**
* Check if the extension launched file exists.
* This is to detect when we are running in a clean install scenario.
Expand Down
62 changes: 62 additions & 0 deletions src/controllers/untitledSqlDocumentService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/* --------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
* ------------------------------------------------------------------------------------------ */

import VscodeWrapper from './vscodeWrapper';
import vscode = require('vscode');
import path = require('path');
import os = require('os');
const fs = require('fs');

/**
* Service for creating untitled documents for SQL query
*/
export default class UntitledSqlDocumentService {
Copy link
Contributor

@benrr101 benrr101 Dec 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure you explored this already, but I feel like there has to be some command built into VS Code to just create an untitled document. I'm thinking by opening an in-memory untitled doc, the header for the file would be "untitled 1" or some such. Can we verify that there isn't any way to programmatically create an untitled document before we go ahead with this change? #WontFix

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that one doesn't let me change the language to sql


In reply to: 94176030 [](ancestors = 94176030)

Copy link
Contributor

@benrr101 benrr101 Dec 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to change the language of the document after it's created? I took a look at the API but it claims that the languageId of a TextDocument is readonly. I can't really understand why that would be the case, and I'd want further clarification from VS Code folks. #WontFix

Copy link
Member Author

@llali llali Jan 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that also and it didn't work. I created a feature request in VsCode: microsoft/vscode#17917 (comment)
#WontFix

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, seems silly they don't have a programmatic way of doing that, but it's a project that's still growing.

private _counter: number = 1;

constructor(private vscodeWrapper: VscodeWrapper) {
}

/**
* Creates new untitled document for SQL query and opens in new editor tab
*/
public newQuery(): Promise<boolean> {

return new Promise<boolean>((resolve, reject) => {
try {
let filePath = this.createUntitledFilePath();
let docUri: vscode.Uri = vscode.Uri.parse('untitled:' + filePath);

// Open an untitled document. So the file doesn't have to exist in disk
this.vscodeWrapper.openTextDocument(docUri).then(doc => {
// Show the new untitled document in the editor's first tab and change the focus to it.
this.vscodeWrapper.showTextDocument(doc, 1, false).then(textDoc => {
this._counter++;
resolve(true);
});
});
} catch (error) {
reject(error);
}
});
}

private createUntitledFilePath(): string {
let filePath = UntitledSqlDocumentService.createFilePath(this._counter);
while (fs.existsSync(filePath)) {
this._counter++;
filePath = UntitledSqlDocumentService.createFilePath(this._counter);
}
while (this.vscodeWrapper.textDocuments.find(x => x.fileName.toUpperCase() === filePath.toUpperCase())) {
this._counter++;
filePath = UntitledSqlDocumentService.createFilePath(this._counter);
}
return filePath;
}

public static createFilePath(counter: number): string {
return path.join(os.tmpdir(), `SQLQuery${counter}.sql`);
}
}

1 change: 1 addition & 0 deletions src/models/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const cmdDisconnect = 'extension.disconnect';
export const cmdChooseDatabase = 'extension.chooseDatabase';
export const cmdShowReleaseNotes = 'extension.showReleaseNotes';
export const cmdShowGettingStarted = 'extension.showGettingStarted';
export const cmdNewQuery = 'extension.newQuery';
export const cmdManageConnectionProfiles = 'extension.manageProfiles';

export const sqlDbPrefix = '.database.windows.net';
Expand Down
27 changes: 27 additions & 0 deletions test/mainController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as TypeMoq from 'typemoq';
import vscode = require('vscode');
import MainController from '../src/controllers/mainController';
import ConnectionManager from '../src/controllers/connectionManager';
import UntitledSqlDocumentService from '../src/controllers/untitledSqlDocumentService';
import * as Extension from '../src/extension';
import Constants = require('../src/models/constants');
import assert = require('assert');
Expand All @@ -13,6 +14,7 @@ suite('MainController Tests', () => {
let newDocument: vscode.TextDocument;
let mainController: MainController;
let connectionManager: TypeMoq.Mock<ConnectionManager>;
let untitledSqlDocumentService: TypeMoq.Mock<UntitledSqlDocumentService>;
let docUri: string;
let newDocUri: string;
let docUriCallback: string;
Expand Down Expand Up @@ -53,6 +55,9 @@ suite('MainController Tests', () => {
connectionManager = TypeMoq.Mock.ofType(ConnectionManager);
mainController.connectionManager = connectionManager.object;

untitledSqlDocumentService = TypeMoq.Mock.ofType(UntitledSqlDocumentService);
mainController.untitledSqlDocumentService = untitledSqlDocumentService.object;

// Watching these functions and input paramters
connectionManager.setup(x => x.onDidOpenTextDocument(TypeMoq.It.isAny())).callback((doc) => {
docUriCallback = doc.uri.toString();
Expand Down Expand Up @@ -173,4 +178,26 @@ suite('MainController Tests', () => {
}
});

test('onNewQuery should call the new query and new connection' , () => {

untitledSqlDocumentService.setup(x => x.newQuery()).returns(() => Promise.resolve(true));
connectionManager.setup(x => x.onNewConnection()).returns(() => Promise.resolve(true));

return mainController.onNewQuery().then(result => {
untitledSqlDocumentService.verify(x => x.newQuery(), TypeMoq.Times.once());
connectionManager.verify(x => x.onNewConnection(), TypeMoq.Times.once());
});
});

test('onNewQuery should not call the new connection if new query fails' , done => {

untitledSqlDocumentService.setup(x => x.newQuery()).returns(() => { return Promise.reject<boolean>('error'); } );
connectionManager.setup(x => x.onNewConnection()).returns(() => { return Promise.resolve(true); } );

mainController.onNewQuery().catch(error => {
untitledSqlDocumentService.verify(x => x.newQuery(), TypeMoq.Times.once());
connectionManager.verify(x => x.onNewConnection(), TypeMoq.Times.never());
done();
});
});
});
128 changes: 128 additions & 0 deletions test/untitledSqlDocumentService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import * as TypeMoq from 'typemoq';
Copy link
Contributor

@benrr101 benrr101 Dec 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see there's a good number of tests added here, but Coveralls says this change drops code coverage by 32%. That's a huge change. Can we make sure we understand what's going on with coveralls's assessment before we move forward? #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes not sure why. I see a permission error in travis job, might be related


In reply to: 94176306 [](ancestors = 94176306)

import vscode = require('vscode');
import UntitledSqlDocumentService from '../src/controllers/untitledSqlDocumentService';
import VscodeWrapper from '../src/controllers/vscodeWrapper';
const fse = require('fs-extra');
const fs = require('fs');

interface IFixture {
openDocResult: Promise<vscode.TextDocument>;
showDocResult: Promise<vscode.TextEditor>;
vscodeWrapper: TypeMoq.Mock<VscodeWrapper>;
service: UntitledSqlDocumentService;
textDocuments: vscode.TextDocument[];
}

suite('UntitledSqlDocumentService Tests', () => {

function createTextDocumentObject(fileName: string = ''): vscode.TextDocument {
return {
uri: undefined,
fileName: fileName,
getText: undefined,
getWordRangeAtPosition: undefined,
isDirty: true,
isUntitled: true,
languageId: 'sql',
lineAt: undefined,
lineCount: undefined,
offsetAt: undefined,
positionAt: undefined,
save: undefined,
validatePosition: undefined,
validateRange: undefined,
version: undefined
};
}

function createUntitledSqlDocumentService(fixture: IFixture): IFixture {
let vscodeWrapper: TypeMoq.Mock<VscodeWrapper>;
vscodeWrapper = TypeMoq.Mock.ofType(VscodeWrapper);

vscodeWrapper.setup(x => x.textDocuments).returns(() => { return fixture.textDocuments; });
vscodeWrapper.setup(x => x.openTextDocument(TypeMoq.It.isAny()))
.returns(() => { return Promise.resolve(createTextDocumentObject()); });
vscodeWrapper.setup(x => x.showTextDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
.returns(() => { return Promise.resolve(TypeMoq.It.isAny()); });
fixture.vscodeWrapper = vscodeWrapper;
fixture.service = new UntitledSqlDocumentService(vscodeWrapper.object);
return fixture;
}

test('newQuery should open a new untitled document and show in new tab' , () => {
let fixture: IFixture = {
openDocResult: Promise.resolve(createTextDocumentObject()),
showDocResult: Promise.resolve(TypeMoq.It.isAny()),
service: undefined,
vscodeWrapper: undefined,
textDocuments: []
};
fixture = createUntitledSqlDocumentService(fixture);

return fixture.service.newQuery().then(result => {
fixture.vscodeWrapper.verify(x => x.openTextDocument(
TypeMoq.It.is<vscode.Uri>(d => d.scheme === 'untitled')), TypeMoq.Times.once());
fixture.vscodeWrapper.verify(x => x.showTextDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
});
});

test('newQuery should increment the counter for untitled document if file exits' , () => {
let fixture: IFixture = {
openDocResult: Promise.resolve(createTextDocumentObject()),
showDocResult: Promise.resolve(TypeMoq.It.isAny()),
service: undefined,
vscodeWrapper: undefined,
textDocuments: []
};
let counter = getCounterForUntitledFile(1);
fixture = createUntitledSqlDocumentService(fixture);
let filePath = UntitledSqlDocumentService.createFilePath(counter);
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, 'test');
}
return fixture.service.newQuery().then(result => {
fixture.vscodeWrapper.verify(x => x.openTextDocument(
TypeMoq.It.is<vscode.Uri>(d => verifyDocumentUri(d, counter + 1))), TypeMoq.Times.once());
fixture.vscodeWrapper.verify(x => x.showTextDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
if (!fs.existsSync(filePath)) {
fse.remove(filePath, undefined);
}
});
});

function verifyDocumentUri(uri: vscode.Uri, expectedNumber: number): boolean {
return uri.scheme === 'untitled' && uri.path.endsWith(`${expectedNumber}.sql`);
}

function getCounterForUntitledFile(start: number): number {
let counter = start;
let filePath = UntitledSqlDocumentService.createFilePath(counter);
while (fs.existsSync(filePath)) {
counter++;
filePath = UntitledSqlDocumentService.createFilePath(counter);
}
return counter;
}

test('newQuery should increment the counter for untitled document given text documents already open with current counter' , () => {
let counter = getCounterForUntitledFile(1);
let fixture: IFixture = {
openDocResult: Promise.resolve(createTextDocumentObject()),
showDocResult: Promise.resolve(TypeMoq.It.isAny()),
service: undefined,
vscodeWrapper: undefined,
textDocuments: [
createTextDocumentObject(UntitledSqlDocumentService.createFilePath(counter + 1)),
createTextDocumentObject(UntitledSqlDocumentService.createFilePath(counter))]
};
fixture = createUntitledSqlDocumentService(fixture);
let service = fixture.service;

return service.newQuery().then(result => {
fixture.vscodeWrapper.verify(x => x.openTextDocument(
TypeMoq.It.is<vscode.Uri>(d => verifyDocumentUri(d, counter + 2))), TypeMoq.Times.once());
fixture.vscodeWrapper.verify(x => x.showTextDocument(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
});
});
});