mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-24 01:25:37 -05:00
More layering (#9111)
* move handling generated files to the serilization classes * remove unneeded methods * add more folders to strictire compile, add more strict compile options * update ci * wip * add more layering and fix issues * add more strictness * remove unnecessary assertion * add missing checks * fix indentation * wip * remove jsdoc * fix layering * fix compile * fix compile errors * wip * wip * finish layering * fix css * more layering * rip * reworking results serializer * move some files around * move capabilities to platform wip * implement capabilities register provider * fix capabilities service * fix usage of the regist4ry * add contribution * remove no longer good parts * fix issues with startup * another try * fix startup * fix imports * fix tests * fix tests * fix more tests * fix tests * fix more tests * fix broken test * fix tabbing * fix naming
This commit is contained in:
161
src/sql/workbench/services/query/common/dataService.ts
Normal file
161
src/sql/workbench/services/query/common/dataService.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditUpdateCellResult, EditSubsetResult, EditCreateRowResult } from 'azdata';
|
||||
import { IQueryModelService } from 'sql/workbench/services/query/common/queryModel';
|
||||
import { ResultSerializer, ISaveRequest } from 'sql/workbench/services/query/common/resultSerializer';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
/**
|
||||
* DataService handles the interactions between QueryModel and app.component. Thus, it handles
|
||||
* query running and grid interaction communication for a single URI.
|
||||
*/
|
||||
export class DataService {
|
||||
|
||||
public fireQueryEvent(event: any) {
|
||||
this._queryEvents.fire(event);
|
||||
}
|
||||
private readonly _queryEvents = new Emitter<any>();
|
||||
public readonly queryEvents = this._queryEvents.event;
|
||||
|
||||
public fireGridContent(event: any) {
|
||||
this._gridContent.fire(event);
|
||||
}
|
||||
private readonly _gridContent = new Emitter<any>();
|
||||
public readonly gridContent = this._gridContent.event;
|
||||
|
||||
private editQueue: Promise<any>;
|
||||
|
||||
constructor(
|
||||
private _uri: string,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IQueryModelService private _queryModel: IQueryModelService
|
||||
) {
|
||||
this.editQueue = Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specified number of rows starting at a specified row. Should only
|
||||
* be used for edit sessions.
|
||||
* @param rowStart The row to start retrieving from (inclusive)
|
||||
* @param numberOfRows The maximum number of rows to return
|
||||
*/
|
||||
getEditRows(rowStart: number, numberOfRows: number): Promise<EditSubsetResult | undefined> {
|
||||
return this._queryModel.getEditRows(this._uri, rowStart, numberOfRows);
|
||||
}
|
||||
|
||||
updateCell(rowId: number, columnId: number, newValue: string): Thenable<EditUpdateCellResult> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.updateCell(self._uri, rowId, columnId, newValue).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
commitEdit(): Thenable<void> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.commitEdit(self._uri).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
createRow(): Thenable<EditCreateRowResult> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.createRow(self._uri).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
deleteRow(rowId: number): Thenable<void> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.deleteRow(self._uri, rowId).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
self._queryModel.showCommitError(error.message);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
revertCell(rowId: number, columnId: number): Thenable<void> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.revertCell(self._uri, rowId, columnId).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
revertRow(rowId: number): Thenable<void> {
|
||||
const self = this;
|
||||
self.editQueue = self.editQueue.then(() => {
|
||||
return self._queryModel.revertRow(self._uri, rowId).then(result => {
|
||||
return result;
|
||||
}, error => {
|
||||
// Start our editQueue over due to the rejected promise
|
||||
self.editQueue = Promise.resolve();
|
||||
return Promise.reject(error);
|
||||
});
|
||||
});
|
||||
return self.editQueue;
|
||||
}
|
||||
|
||||
/**
|
||||
* send request to save the selected result set as csv
|
||||
* @param uri of the calling document
|
||||
* @param batchId The batch id of the batch with the result to save
|
||||
* @param resultId The id of the result to save as csv
|
||||
*/
|
||||
sendSaveRequest(saveRequest: ISaveRequest): void {
|
||||
let serializer = this._instantiationService.createInstance(ResultSerializer);
|
||||
serializer.saveResults(this._uri, saveRequest);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a copy request
|
||||
* @param selection The selection range to copy
|
||||
* @param batchId The batch id of the result to copy from
|
||||
* @param resultId The result id of the result to copy from
|
||||
* @param includeHeaders [Optional]: Should column headers be included in the copy selection
|
||||
*/
|
||||
copyResults(selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void {
|
||||
this._queryModel.copyResults(this._uri, selection, batchId, resultId, includeHeaders);
|
||||
}
|
||||
|
||||
onLoaded(): void {
|
||||
this._queryModel.onLoaded(this._uri);
|
||||
}
|
||||
}
|
||||
22
src/sql/workbench/services/query/common/gridContentEvents.ts
Normal file
22
src/sql/workbench/services/query/common/gridContentEvents.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const ResizeContents = 'ResizeContents';
|
||||
export const RefreshContents = 'RefreshContents';
|
||||
export const ToggleResultPane = 'ToggleResultPane';
|
||||
export const ToggleMessagePane = 'ToggleMessagePane';
|
||||
export const CopySelection = 'CopySelection';
|
||||
export const CopyWithHeaders = 'CopyWithHeaders';
|
||||
export const CopyMessagesSelection = 'CopyMessagesSelection';
|
||||
export const SelectAll = 'SelectAll';
|
||||
export const SelectAllMessages = 'SelectAllMessages';
|
||||
export const SaveAsCsv = 'SaveAsCSV';
|
||||
export const SaveAsJSON = 'SaveAsJSON';
|
||||
export const SaveAsExcel = 'SaveAsExcel';
|
||||
export const SaveAsXML = 'SaveAsXML';
|
||||
export const ViewAsChart = 'ViewAsChart';
|
||||
export const ViewAsVisualizer = 'ViewAsVisualizer';
|
||||
export const GoToNextQueryOutputTab = 'GoToNextQueryOutputTab';
|
||||
export const GoToNextGrid = 'GoToNextGrid';
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
|
||||
import { SaveFormat } from 'sql/workbench/services/query/common/resultSerializer';
|
||||
|
||||
export interface IGridDataProvider {
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import QueryRunner, { IQueryMessage } from 'sql/workbench/services/query/common/queryRunner';
|
||||
import { DataService } from 'sql/workbench/contrib/grid/common/dataService';
|
||||
import { DataService } from 'sql/workbench/services/query/common/dataService';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
|
||||
import {
|
||||
ISelectionData,
|
||||
ResultSetSubset,
|
||||
@@ -52,9 +51,9 @@ export interface IQueryModelService {
|
||||
getQueryRunner(uri: string): QueryRunner | undefined;
|
||||
|
||||
getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<ResultSetSubset | undefined>;
|
||||
runQuery(uri: string, selection: ISelectionData | undefined, queryInput: QueryEditorInput, runOptions?: ExecutionPlanOptions): void;
|
||||
runQueryStatement(uri: string, selection: ISelectionData | undefined, queryInput: QueryEditorInput): void;
|
||||
runQueryString(uri: string, selection: string | undefined, queryInput: QueryEditorInput): void;
|
||||
runQuery(uri: string, selection: ISelectionData | undefined, runOptions?: ExecutionPlanOptions): void;
|
||||
runQueryStatement(uri: string, selection: ISelectionData | undefined): void;
|
||||
runQueryString(uri: string, selection: string | undefined): void;
|
||||
cancelQuery(input: QueryRunner | string): void;
|
||||
disposeQuery(uri: string): void;
|
||||
isRunningQuery(uri: string): boolean;
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as GridContentEvents from 'sql/workbench/contrib/grid/common/gridContentEvents';
|
||||
import * as LocalizedConstants from 'sql/workbench/contrib/query/common/localizedConstants';
|
||||
import * as GridContentEvents from 'sql/workbench/services/query/common/gridContentEvents';
|
||||
import QueryRunner from 'sql/workbench/services/query/common/queryRunner';
|
||||
import { DataService } from 'sql/workbench/contrib/grid/common/dataService';
|
||||
import { DataService } from 'sql/workbench/services/query/common/dataService';
|
||||
import { IQueryModelService, IQueryEvent } from 'sql/workbench/services/query/common/queryModel';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
@@ -18,7 +17,6 @@ import * as strings from 'vs/base/common/strings';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
|
||||
|
||||
const selectionSnippetMaxLen = 100;
|
||||
|
||||
@@ -35,7 +33,6 @@ export class QueryInfo {
|
||||
public dataService: DataService;
|
||||
public queryEventQueue: QueryEvent[];
|
||||
public selection: Array<azdata.ISelectionData>;
|
||||
public queryInput: QueryEditorInput;
|
||||
public selectionSnippet?: string;
|
||||
|
||||
// Notes if the angular components have obtained the DataService. If not, all messages sent
|
||||
@@ -173,28 +170,28 @@ export class QueryModelService implements IQueryModelService {
|
||||
/**
|
||||
* Run a query for the given URI with the given text selection
|
||||
*/
|
||||
public async runQuery(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
|
||||
return this.doRunQuery(uri, selection, queryInput, false, runOptions);
|
||||
public async runQuery(uri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
|
||||
return this.doRunQuery(uri, selection, false, runOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the current SQL statement for the given URI
|
||||
*/
|
||||
public async runQueryStatement(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput): Promise<void> {
|
||||
return this.doRunQuery(uri, selection, queryInput, true);
|
||||
public async runQueryStatement(uri: string, selection: azdata.ISelectionData): Promise<void> {
|
||||
return this.doRunQuery(uri, selection, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the current SQL statement for the given URI
|
||||
*/
|
||||
public async runQueryString(uri: string, selection: string, queryInput: QueryEditorInput): Promise<void> {
|
||||
return this.doRunQuery(uri, selection, queryInput, true);
|
||||
public async runQueryString(uri: string, selection: string): Promise<void> {
|
||||
return this.doRunQuery(uri, selection, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Query implementation
|
||||
*/
|
||||
private async doRunQuery(uri: string, selection: azdata.ISelectionData | string, queryInput: QueryEditorInput,
|
||||
private async doRunQuery(uri: string, selection: azdata.ISelectionData | string,
|
||||
runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
|
||||
// Reuse existing query runner if it exists
|
||||
let queryRunner: QueryRunner | undefined;
|
||||
@@ -220,8 +217,6 @@ export class QueryModelService implements IQueryModelService {
|
||||
queryRunner = info.queryRunner;
|
||||
}
|
||||
|
||||
info.queryInput = queryInput;
|
||||
|
||||
if (types.isString(selection)) {
|
||||
// Run the query string in this case
|
||||
if (selection.length < selectionSnippetMaxLen) {
|
||||
@@ -245,7 +240,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
});
|
||||
queryRunner.onBatchStart(b => {
|
||||
let link = undefined;
|
||||
let messageText = LocalizedConstants.runQueryBatchStartMessage;
|
||||
let messageText = nls.localize('runQueryBatchStartMessage', "Started executing query at ");
|
||||
if (b.selection) {
|
||||
if (info.selectionSnippet) {
|
||||
// This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the
|
||||
@@ -253,7 +248,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet);
|
||||
} else {
|
||||
link = {
|
||||
text: strings.format(LocalizedConstants.runQueryBatchStartLine, b.selection.startLine + 1)
|
||||
text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), b.selection.startLine + 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -382,7 +377,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
// can be correct
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: strings.format(LocalizedConstants.msgCancelQueryFailed, error)
|
||||
message: strings.format(nls.localize('msgCancelQueryFailed', "Canceling the query failed: {0}"), error)
|
||||
});
|
||||
this._fireQueryEvent(queryRunner!.uri, 'complete', 0);
|
||||
});
|
||||
@@ -432,7 +427,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
});
|
||||
queryRunner.onBatchStart(batch => {
|
||||
let link = undefined;
|
||||
let messageText = LocalizedConstants.runQueryBatchStartMessage;
|
||||
let messageText = nls.localize('runQueryBatchStartMessage', "Started executing query at ");
|
||||
if (batch.selection) {
|
||||
if (info.selectionSnippet) {
|
||||
// This indicates it's a query string. Do not include line information since it'll be inaccurate, but show some of the
|
||||
@@ -440,7 +435,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet);
|
||||
} else {
|
||||
link = {
|
||||
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1)
|
||||
text: strings.format(nls.localize('runQueryBatchStartLine', "Line {0}"), batch.selection.startLine + 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -622,7 +617,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
if (service) {
|
||||
// There is no need to queue up these events like there is for the query events because
|
||||
// if the DataService is not yet ready there will be no grid content to update
|
||||
service.gridContentObserver.next(type);
|
||||
service.fireGridContent(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -632,7 +627,7 @@ export class QueryModelService implements IQueryModelService {
|
||||
|
||||
if (info && info.dataServiceReady) {
|
||||
let service: DataService = this.getDataService(uri);
|
||||
service.queryEventObserver.next({
|
||||
service.fireQueryEvent({
|
||||
type: type,
|
||||
data: data
|
||||
});
|
||||
|
||||
@@ -7,10 +7,9 @@ import * as azdata from 'azdata';
|
||||
|
||||
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
|
||||
import * as Utils from 'sql/platform/connection/common/utils';
|
||||
import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { IQueryPlanInfo } from 'sql/workbench/services/query/common/queryModel';
|
||||
import { ResultSerializer } from 'sql/workbench/contrib/query/common/resultSerializer';
|
||||
import { ResultSerializer, SaveFormat } from 'sql/workbench/services/query/common/resultSerializer';
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
365
src/sql/workbench/services/query/common/resultSerializer.ts
Normal file
365
src/sql/workbench/services/query/common/resultSerializer.ts
Normal file
@@ -0,0 +1,365 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SaveResultsRequestParams } from 'azdata';
|
||||
import { IQueryManagementService } from 'sql/workbench/services/query/common/queryManagement';
|
||||
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as path from 'vs/base/common/path';
|
||||
import * as nls from 'vs/nls';
|
||||
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { getBaseLabel } from 'vs/base/common/labels';
|
||||
import { ShowFileInFolderAction, OpenFileInFolderAction } from 'sql/workbench/common/workspaceActions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { getRootPath, resolveCurrentDirectory, resolveFilePath } from 'sql/platform/common/pathUtilities';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
let prevSavePath: string;
|
||||
|
||||
export interface ISaveRequest {
|
||||
format: SaveFormat;
|
||||
batchIndex: number;
|
||||
resultSetNumber: number;
|
||||
selection: Slick.Range[];
|
||||
}
|
||||
|
||||
export interface SaveResultsResponse {
|
||||
succeeded: boolean;
|
||||
messages?: string;
|
||||
}
|
||||
|
||||
interface ICsvConfig {
|
||||
includeHeaders: boolean;
|
||||
delimiter: string;
|
||||
lineSeperator: string;
|
||||
textIdentifier: string;
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
interface IXmlConfig {
|
||||
formatted: boolean;
|
||||
encoding: string;
|
||||
}
|
||||
|
||||
export enum SaveFormat {
|
||||
CSV = 'csv',
|
||||
JSON = 'json',
|
||||
EXCEL = 'excel',
|
||||
XML = 'xml'
|
||||
}
|
||||
|
||||
const msgSaveFailed = nls.localize('msgSaveFailed', "Failed to save results. ");
|
||||
const msgSaveSucceeded = nls.localize('msgSaveSucceeded', "Successfully saved results to ");
|
||||
|
||||
/**
|
||||
* Handles save results request from the context menu of slickGrid
|
||||
*/
|
||||
export class ResultSerializer {
|
||||
public static tempFileCount: number = 1;
|
||||
|
||||
constructor(
|
||||
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@IEditorService private _editorService: IEditorService,
|
||||
@IWorkspaceContextService private _contextService: IWorkspaceContextService,
|
||||
@IFileDialogService private readonly fileDialogService: IFileDialogService,
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Handle save request by getting filename from user and sending request to service
|
||||
*/
|
||||
public saveResults(uri: string, saveRequest: ISaveRequest): Promise<void> {
|
||||
const self = this;
|
||||
return this.promptForFilepath(saveRequest.format, uri).then(filePath => {
|
||||
if (filePath) {
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
filePath = resolveFilePath(uri, filePath, this.rootPath)!;
|
||||
}
|
||||
let saveResultsParams = this.getParameters(uri, filePath, saveRequest.batchIndex, saveRequest.resultSetNumber, saveRequest.format, saveRequest.selection ? saveRequest.selection[0] : undefined);
|
||||
let sendRequest = () => this.sendSaveRequestToService(saveResultsParams);
|
||||
return self.doSave(filePath, saveRequest.format, sendRequest);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
private async sendSaveRequestToService(saveResultsParams: SaveResultsRequestParams): Promise<SaveResultsResponse> {
|
||||
let result = await this._queryManagementService.saveResults(saveResultsParams);
|
||||
return {
|
||||
succeeded: !result.messages,
|
||||
messages: result.messages
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle save request by getting filename from user and sending request to service
|
||||
*/
|
||||
public handleSerialization(uri: string, format: SaveFormat, sendRequest: ((filePath: string) => Promise<SaveResultsResponse | undefined>)): Thenable<void> {
|
||||
const self = this;
|
||||
return this.promptForFilepath(format, uri).then(filePath => {
|
||||
if (filePath) {
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
filePath = resolveFilePath(uri, filePath, this.rootPath)!;
|
||||
}
|
||||
return self.doSave(filePath, format, () => sendRequest(filePath!));
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
private get rootPath(): string | undefined {
|
||||
return getRootPath(this._contextService);
|
||||
}
|
||||
|
||||
private promptForFilepath(format: SaveFormat, resourceUri: string): Promise<string | undefined> {
|
||||
let filepathPlaceHolder = prevSavePath ? path.dirname(prevSavePath) : resolveCurrentDirectory(resourceUri, this.rootPath);
|
||||
if (filepathPlaceHolder) {
|
||||
filepathPlaceHolder = path.join(filepathPlaceHolder, this.getResultsDefaultFilename(format));
|
||||
}
|
||||
|
||||
return this.fileDialogService.showSaveDialog({
|
||||
title: nls.localize('resultsSerializer.saveAsFileTitle', "Choose Results File"),
|
||||
defaultUri: filepathPlaceHolder ? URI.file(filepathPlaceHolder) : undefined,
|
||||
filters: this.getResultsFileExtension(format)
|
||||
}).then(filePath => {
|
||||
if (filePath) {
|
||||
prevSavePath = filePath.fsPath;
|
||||
return filePath.fsPath;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private getResultsDefaultFilename(format: SaveFormat): string {
|
||||
let fileName = 'Results';
|
||||
switch (format) {
|
||||
case SaveFormat.CSV:
|
||||
fileName = fileName + '.csv';
|
||||
break;
|
||||
case SaveFormat.JSON:
|
||||
fileName = fileName + '.json';
|
||||
break;
|
||||
case SaveFormat.EXCEL:
|
||||
fileName = fileName + '.xlsx';
|
||||
break;
|
||||
case SaveFormat.XML:
|
||||
fileName = fileName + '.xml';
|
||||
break;
|
||||
default:
|
||||
fileName = fileName + '.txt';
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
private getResultsFileExtension(format: SaveFormat): FileFilter[] {
|
||||
let fileFilters = new Array<FileFilter>();
|
||||
let fileFilter: { extensions: string[]; name: string } = Object.create(null);
|
||||
|
||||
switch (format) {
|
||||
case SaveFormat.CSV:
|
||||
fileFilter.name = nls.localize('resultsSerializer.saveAsFileExtensionCSVTitle', "CSV (Comma delimited)");
|
||||
fileFilter.extensions = ['csv'];
|
||||
break;
|
||||
case SaveFormat.JSON:
|
||||
fileFilter.name = nls.localize('resultsSerializer.saveAsFileExtensionJSONTitle', "JSON");
|
||||
fileFilter.extensions = ['json'];
|
||||
break;
|
||||
case SaveFormat.EXCEL:
|
||||
fileFilter.name = nls.localize('resultsSerializer.saveAsFileExtensionExcelTitle', "Excel Workbook");
|
||||
fileFilter.extensions = ['xlsx'];
|
||||
break;
|
||||
case SaveFormat.XML:
|
||||
fileFilter.name = nls.localize('resultsSerializer.saveAsFileExtensionXMLTitle', "XML");
|
||||
fileFilter.extensions = ['xml'];
|
||||
break;
|
||||
default:
|
||||
fileFilter.name = nls.localize('resultsSerializer.saveAsFileExtensionTXTTitle', "Plain Text");
|
||||
fileFilter.extensions = ['txt'];
|
||||
}
|
||||
|
||||
fileFilters.push(fileFilter);
|
||||
return fileFilters;
|
||||
}
|
||||
|
||||
public getBasicSaveParameters(format: string): SaveResultsRequestParams {
|
||||
let saveResultsParams: SaveResultsRequestParams;
|
||||
|
||||
if (format === SaveFormat.CSV) {
|
||||
saveResultsParams = this.getConfigForCsv();
|
||||
} else if (format === SaveFormat.JSON) {
|
||||
saveResultsParams = this.getConfigForJson();
|
||||
} else if (format === SaveFormat.EXCEL) {
|
||||
saveResultsParams = this.getConfigForExcel();
|
||||
} else if (format === SaveFormat.XML) {
|
||||
saveResultsParams = this.getConfigForXml();
|
||||
}
|
||||
return saveResultsParams!; // this could be unsafe
|
||||
}
|
||||
|
||||
|
||||
private getConfigForCsv(): SaveResultsRequestParams {
|
||||
let saveResultsParams = <SaveResultsRequestParams>{ resultFormat: SaveFormat.CSV as string };
|
||||
|
||||
// get save results config from vscode config
|
||||
let saveConfig = this._configurationService.getValue<ICsvConfig>('sql.saveAsCsv');
|
||||
// if user entered config, set options
|
||||
if (saveConfig) {
|
||||
if (saveConfig.includeHeaders !== undefined) {
|
||||
saveResultsParams.includeHeaders = saveConfig.includeHeaders;
|
||||
}
|
||||
if (saveConfig.delimiter !== undefined) {
|
||||
saveResultsParams.delimiter = saveConfig.delimiter;
|
||||
}
|
||||
if (saveConfig.lineSeperator !== undefined) {
|
||||
saveResultsParams.lineSeperator = saveConfig.lineSeperator;
|
||||
}
|
||||
if (saveConfig.textIdentifier !== undefined) {
|
||||
saveResultsParams.textIdentifier = saveConfig.textIdentifier;
|
||||
}
|
||||
if (saveConfig.encoding !== undefined) {
|
||||
saveResultsParams.encoding = saveConfig.encoding;
|
||||
}
|
||||
}
|
||||
|
||||
return saveResultsParams;
|
||||
}
|
||||
|
||||
private getConfigForJson(): SaveResultsRequestParams {
|
||||
// JSON does not currently have special conditions
|
||||
let saveResultsParams = <SaveResultsRequestParams>{ resultFormat: SaveFormat.JSON as string };
|
||||
return saveResultsParams;
|
||||
}
|
||||
|
||||
private getConfigForExcel(): SaveResultsRequestParams {
|
||||
// get save results config from vscode config
|
||||
// Note: we are currently using the configSaveAsCsv setting since it has the option mssql.saveAsCsv.includeHeaders
|
||||
// and we want to have just 1 setting that lists this.
|
||||
let config = this.getConfigForCsv();
|
||||
config.resultFormat = SaveFormat.EXCEL;
|
||||
config.delimiter = undefined;
|
||||
config.lineSeperator = undefined;
|
||||
config.textIdentifier = undefined;
|
||||
config.encoding = undefined;
|
||||
return config;
|
||||
}
|
||||
|
||||
private getConfigForXml(): SaveResultsRequestParams {
|
||||
let saveResultsParams = <SaveResultsRequestParams>{ resultFormat: SaveFormat.XML as string };
|
||||
|
||||
// get save results config from vscode config
|
||||
let saveConfig = this._configurationService.getValue<IXmlConfig>('sql.saveAsXml');
|
||||
// if user entered config, set options
|
||||
if (saveConfig) {
|
||||
if (saveConfig.formatted !== undefined) {
|
||||
saveResultsParams.formatted = saveConfig.formatted;
|
||||
}
|
||||
if (saveConfig.encoding !== undefined) {
|
||||
saveResultsParams.encoding = saveConfig.encoding;
|
||||
}
|
||||
}
|
||||
|
||||
return saveResultsParams;
|
||||
}
|
||||
|
||||
|
||||
private getParameters(uri: string, filePath: string, batchIndex: number, resultSetNo: number, format: string, selection?: Slick.Range): SaveResultsRequestParams {
|
||||
let saveResultsParams = this.getBasicSaveParameters(format);
|
||||
saveResultsParams.filePath = filePath;
|
||||
saveResultsParams.ownerUri = uri;
|
||||
saveResultsParams.resultSetIndex = resultSetNo;
|
||||
saveResultsParams.batchIndex = batchIndex;
|
||||
if (this.isSelected(selection)) {
|
||||
saveResultsParams.rowStartIndex = selection.fromRow;
|
||||
saveResultsParams.rowEndIndex = selection.toRow;
|
||||
saveResultsParams.columnStartIndex = selection.fromCell;
|
||||
saveResultsParams.columnEndIndex = selection.toCell;
|
||||
}
|
||||
return saveResultsParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a range of cells were selected.
|
||||
*/
|
||||
private isSelected(selection?: Slick.Range): selection is Slick.Range {
|
||||
return !!(selection && !((selection.fromCell === selection.toCell) && (selection.fromRow === selection.toRow)));
|
||||
}
|
||||
|
||||
|
||||
private promptFileSavedNotification(savedFilePath: string) {
|
||||
let label = getBaseLabel(path.dirname(savedFilePath));
|
||||
|
||||
this._notificationService.prompt(
|
||||
Severity.Info,
|
||||
msgSaveSucceeded + savedFilePath,
|
||||
[{
|
||||
label: nls.localize('openLocation', "Open file location"),
|
||||
run: () => {
|
||||
let action = this._instantiationService.createInstance(ShowFileInFolderAction, savedFilePath, label || path.sep);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
}, {
|
||||
label: nls.localize('openFile', "Open file"),
|
||||
run: () => {
|
||||
let action = this._instantiationService.createInstance(OpenFileInFolderAction, savedFilePath, label || path.sep);
|
||||
action.run();
|
||||
action.dispose();
|
||||
}
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send request to sql tools service to save a result set
|
||||
*/
|
||||
private async doSave(filePath: string, format: string, sendRequest: () => Promise<SaveResultsResponse | undefined>): Promise<void> {
|
||||
|
||||
// send message to the sqlserverclient for converting results to the requested format and saving to filepath
|
||||
try {
|
||||
let result = await sendRequest();
|
||||
if (!result || result.messages) {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: msgSaveFailed + (result ? result.messages : '')
|
||||
});
|
||||
} else {
|
||||
this.promptFileSavedNotification(filePath);
|
||||
this.openSavedFile(filePath, format);
|
||||
}
|
||||
// TODO telemetry for save results
|
||||
// Telemetry.sendTelemetryEvent('SavedResults', { 'type': format });
|
||||
|
||||
} catch (error) {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: msgSaveFailed + error
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the saved file in a new vscode editor pane
|
||||
*/
|
||||
private openSavedFile(filePath: string, format: string): void {
|
||||
if (format !== SaveFormat.EXCEL) {
|
||||
let uri = URI.file(filePath);
|
||||
this._editorService.openEditor({ resource: uri }).then((result) => {
|
||||
|
||||
}, (error: any) => {
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: error
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user