More layering and compile strictness (#8973)

* 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

* remove jsdoc
This commit is contained in:
Anthony Dresser
2020-01-29 20:35:11 -08:00
committed by GitHub
parent ddfdd43fc3
commit 56695be14a
185 changed files with 725 additions and 635 deletions

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Subscription } from 'rxjs/Subscription';
import { Event } from 'vs/base/common/event';
const ANGULAREVENTING_SERVICE_ID = 'angularEventingService';
export const IAngularEventingService = createDecorator<IAngularEventingService>(ANGULAREVENTING_SERVICE_ID);
@@ -33,9 +33,8 @@ export interface IAngularEventingService {
/**
* Adds a listener for the dashboard to send events, should only be called once for each dashboard by the dashboard itself
* @param uri Uri of the dashboard
* @param cb Listening function
*/
onAngularEvent(uri: string, cb: (event: IAngularEvent) => void): Subscription;
onAngularEvent(uri: string): Event<IAngularEvent>;
/**
* Send an event to the dashboard; no op if the dashboard has not started listening yet

View File

@@ -3,36 +3,33 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import { IAngularEventingService, IAngularEvent, AngularEventType } from 'sql/platform/angularEventing/browser/angularEventingService';
import { ILogService } from 'vs/platform/log/common/log';
import { Event, Emitter } from 'vs/base/common/event';
export class AngularEventingService implements IAngularEventingService {
public _serviceBrand: undefined;
private _angularMap = new Map<string, Subject<IAngularEvent>>();
private _angularMap = new Map<string, Emitter<IAngularEvent>>();
constructor(
@ILogService private readonly logService: ILogService
) { }
public onAngularEvent(uri: string, cb: (event: IAngularEvent) => void): Subscription {
let subject = this._angularMap.get(uri);
if (!subject) {
subject = new Subject<IAngularEvent>();
this._angularMap.set(uri, subject);
public onAngularEvent(uri: string): Event<IAngularEvent> {
let emitter = this._angularMap.get(uri);
if (!emitter) {
emitter = new Emitter<IAngularEvent>();
this._angularMap.set(uri, emitter);
}
let sub = subject.subscribe(cb);
return sub;
return emitter.event;
}
public sendAngularEvent(uri: string, event: AngularEventType, payload?: any): void {
const subject = this._angularMap.get(uri);
if (!subject) {
const emitter = this._angularMap.get(uri);
if (!emitter) {
this.logService.warn('Got request to send an event to a dashboard that has not started listening');
} else {
subject.next({ event, payload });
emitter.fire({ event, payload });
}
}
}

View File

@@ -6,11 +6,11 @@
import * as azdata from 'azdata';
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
import { ICapabilitiesService, ProviderFeatures } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { Event, Emitter } from 'vs/base/common/event';
import { Action } from 'vs/base/common/actions';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/platform/connection/common/interfaces';
export class TestCapabilitiesService implements ICapabilitiesService {

View File

@@ -12,7 +12,6 @@ import { generateUuid } from 'vs/base/common/uuid';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { isString } from 'vs/base/common/types';
import { deepClone } from 'vs/base/common/objects';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as Constants from 'sql/platform/connection/common/constants';
import { find } from 'vs/base/common/arrays';
@@ -48,7 +47,7 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
let capabilities = this.capabilitiesService.getCapabilities(model.providerName);
if (capabilities && capabilities.connection && capabilities.connection.connectionOptions) {
const options = capabilities.connection.connectionOptions;
let appNameOption = find(options, option => option.specialValueType === ConnectionOptionSpecialType.appName);
let appNameOption = find(options, option => option.specialValueType === interfaces.ConnectionOptionSpecialType.appName);
if (appNameOption) {
let appNameKey = appNameOption.name;
this.options[appNameKey] = Constants.applicationName;

View File

@@ -18,3 +18,22 @@ export interface IConnectionProfileStore {
id: string;
}
export enum ServiceOptionType {
string = 'string',
multistring = 'multistring',
password = 'password',
number = 'number',
category = 'category',
boolean = 'boolean',
object = 'object'
}
export enum ConnectionOptionSpecialType {
connectionName = 'connectionName',
serverName = 'serverName',
databaseName = 'databaseName',
authType = 'authType',
userName = 'userName',
password = 'password',
appName = 'appName'
}

View File

@@ -7,11 +7,11 @@ import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { isString } from 'vs/base/common/types';
import * as azdata from 'azdata';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as Constants from 'sql/platform/connection/common/constants';
import { ICapabilitiesService, ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
import { assign } from 'vs/base/common/objects';
import { find } from 'vs/base/common/arrays';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/platform/connection/common/interfaces';
type SettableProperty = 'serverName' | 'authenticationType' | 'databaseName' | 'password' | 'connectionName' | 'userName';

View File

@@ -9,9 +9,8 @@ import { ICapabilitiesService, ProviderFeatures } from 'sql/platform/capabilitie
import { ConnectionConfig, ISaveGroupResult } from 'sql/platform/connection/common/connectionConfig';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { IConnectionProfile, IConnectionProfileStore } from 'sql/platform/connection/common/interfaces';
import { IConnectionProfile, IConnectionProfileStore, ConnectionOptionSpecialType, ServiceOptionType } from 'sql/platform/connection/common/interfaces';
import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
import * as TypeMoq from 'typemoq';
import { Emitter } from 'vs/base/common/event';

View File

@@ -4,10 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionProfile, IConnectionProfileStore } from 'sql/platform/connection/common/interfaces';
import { IConnectionProfile, IConnectionProfileStore, ServiceOptionType, ConnectionOptionSpecialType } from 'sql/platform/connection/common/interfaces';
import * as azdata from 'azdata';
import * as assert from 'assert';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';

View File

@@ -8,10 +8,9 @@ import * as azdata from 'azdata';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { ConnectionStore } from 'sql/platform/connection/common/connectionStore';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IConnectionProfile, ConnectionOptionSpecialType, ServiceOptionType } from 'sql/platform/connection/common/interfaces';
import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService';
import { TestCredentialsService } from 'sql/platform/credentials/test/common/testCredentialsService';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
import { deepClone, deepFreeze, assign } from 'vs/base/common/objects';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';

View File

@@ -4,10 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import { ProviderConnectionInfo } from 'sql/platform/connection/common/providerConnectionInfo';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IConnectionProfile, ConnectionOptionSpecialType, ServiceOptionType } from 'sql/platform/connection/common/interfaces';
import * as azdata from 'azdata';
import * as assert from 'assert';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { assign } from 'vs/base/common/objects';

View File

@@ -2,12 +2,10 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Type } from '@angular/core';
import * as platform from 'vs/platform/registry/common/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as nls from 'vs/nls';
import { IInsightData } from 'sql/workbench/contrib/charts/browser/interfaces';
import { values } from 'vs/base/common/collections';
export type InsightIdentifier = string;
@@ -62,6 +60,11 @@ export interface ISize {
y: number;
}
export interface IInsightData {
columns: Array<string>;
rows: Array<Array<string>>;
}
export interface IInsightsView {
data: IInsightData;
setConfig?: (config: { [key: string]: any }) => void;
@@ -70,18 +73,22 @@ export interface IInsightsView {
export interface IInsightRegistry {
insightSchema: IJSONSchema;
registerInsight(id: string, description: string, schema: IJSONSchema, ctor: Type<IInsightsView>): InsightIdentifier;
registerInsight(id: string, description: string, schema: IJSONSchema, ctor: IInsightsViewCtor): InsightIdentifier;
registerExtensionInsight(id: string, val: IInsightsConfig): void;
getRegisteredExtensionInsights(id: string): IInsightsConfig;
getCtorFromId(id: string): Type<IInsightsView>;
getAllCtors(): Array<Type<IInsightsView>>;
getCtorFromId(id: string): IInsightsViewCtor;
getAllCtors(): Array<IInsightsViewCtor>;
getAllIds(): Array<string>;
}
interface IInsightsViewCtor {
new(...args: any[]): IInsightsView;
}
class InsightRegistry implements IInsightRegistry {
private _insightSchema: IJSONSchema = { type: 'object', description: nls.localize('schema.dashboardWidgets.InsightsRegistry', "Widget used in the dashboards"), properties: {}, additionalProperties: false };
private _extensionInsights: { [x: string]: IInsightsConfig } = {};
private _idToCtor: { [x: string]: Type<IInsightsView> } = {};
private _idToCtor: { [x: string]: IInsightsViewCtor } = {};
/**
* Register a dashboard widget
@@ -89,7 +96,7 @@ class InsightRegistry implements IInsightRegistry {
* @param description description of the widget
* @param schema config schema of the widget
*/
public registerInsight(id: string, description: string, schema: IJSONSchema, ctor: Type<IInsightsView>): InsightIdentifier {
public registerInsight(id: string, description: string, schema: IJSONSchema, ctor: IInsightsViewCtor): InsightIdentifier {
this._insightSchema.properties![id] = schema;
this._idToCtor[id] = ctor;
return id;
@@ -103,11 +110,11 @@ class InsightRegistry implements IInsightRegistry {
return this._extensionInsights[id];
}
public getCtorFromId(id: string): Type<IInsightsView> {
public getCtorFromId(id: string): IInsightsViewCtor {
return this._idToCtor[id];
}
public getAllCtors(): Array<Type<IInsightsView>> {
public getAllCtors(): Array<IInsightsViewCtor> {
return values(this._idToCtor);
}
@@ -123,7 +130,7 @@ class InsightRegistry implements IInsightRegistry {
const insightRegistry = new InsightRegistry();
platform.Registry.add(Extensions.InsightContribution, insightRegistry);
export function registerInsight(id: string, description: string, schema: IJSONSchema, ctor: Type<IInsightsView>): InsightIdentifier {
export function registerInsight(id: string, description: string, schema: IJSONSchema, ctor: IInsightsViewCtor): InsightIdentifier {
return insightRegistry.registerInsight(id, description, schema, ctor);
}

View File

@@ -0,0 +1,129 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
export interface IComponentEventArgs {
eventType: ComponentEventType;
args: any;
componentId?: string;
}
export enum ComponentEventType {
PropertiesChanged,
onDidChange,
onDidClick,
validityChanged,
onMessage,
onSelectedRowChanged,
onComponentCreated,
onCellAction,
onEnterKeyPressed
}
/**
* Defines a component and can be used to map from the model-backed version of the
* world to the frontend UI;
*
* @export
*/
export interface IComponentDescriptor {
/**
* The type of this component. Used to map to the correct angular selector
* when loading the component
*/
type: string;
/**
* A unique ID for this component
*/
id: string;
}
export interface IModelStore {
/**
* Creates and saves the reference of a component descriptor.
* This can be used during creation of a component later
*/
createComponentDescriptor(type: string, createComponentDescriptor: string): IComponentDescriptor;
/**
* gets the descriptor for a previously created component ID
*/
getComponentDescriptor(componentId: string): IComponentDescriptor;
registerComponent(component: IComponent): void;
unregisterComponent(component: IComponent): void;
getComponent(componentId: string): IComponent;
/**
* Runs on a component immediately if the component exists, or runs on
* registration of the component otherwise
*
* @param componentId unique identifier of the component
* @param action some action to perform
*/
eventuallyRunOnComponent<T>(componentId: string, action: (component: IComponent) => T): Promise<T>;
/**
* Register a callback that will validate components when given a component ID
*/
registerValidationCallback(callback: (componentId: string) => Thenable<boolean>): void;
/**
* Run all validations for the given component and return the new validation value
*/
validate(component: IComponent): Thenable<boolean>;
}
/**
* An instance of a model-backed component. This will be a UI element
*
* @export
*/
export interface IComponent extends IDisposable {
descriptor: IComponentDescriptor;
modelStore: IModelStore;
layout(): void;
registerEventHandler(handler: (event: IComponentEventArgs) => void): IDisposable;
clearContainer?: () => void;
addToContainer?: (componentDescriptor: IComponentDescriptor, config: any, index?: number) => void;
removeFromContainer?: (componentDescriptor: IComponentDescriptor) => void;
setLayout?: (layout: any) => void;
getHtml: () => any;
setProperties?: (properties: { [key: string]: any; }) => void;
enabled: boolean;
readonly valid?: boolean;
validate(): Thenable<boolean>;
setDataProvider(handle: number, componentId: string, context: any): void;
refreshDataProvider(item: any): void;
focus(): void;
}
export enum ModelComponentTypes {
NavContainer,
DivContainer,
FlexContainer,
SplitViewContainer,
Card,
InputBox,
DropDown,
DeclarativeTable,
ListBox,
Button,
CheckBox,
RadioButton,
WebView,
Text,
Table,
DashboardWidget,
DashboardWebview,
Form,
Group,
Toolbar,
LoadingComponent,
TreeComponent,
FileBrowserTree,
Editor,
DiffEditor,
Dom,
Hyperlink,
Image,
RadioCardGroup
}

View File

@@ -2,12 +2,10 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Type } from '@angular/core';
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as platform from 'vs/platform/registry/common/platform';
import { IComponent } from 'sql/workbench/browser/modelComponents/interfaces';
import { values } from 'vs/base/common/collections';
import { IComponent, ModelComponentTypes } from 'sql/platform/dashboard/browser/interfaces';
export type ComponentIdentifier = string;
@@ -15,20 +13,24 @@ export const Extensions = {
ComponentContribution: 'dashboard.contributions.components'
};
interface ComponentCtor {
new(...args: any[]): IComponent;
}
export interface IComponentRegistry {
registerComponentType(id: string, typeMapping: ModelComponentTypes, ctor: Type<IComponent>): ComponentIdentifier;
registerComponentType(id: string, typeMapping: ModelComponentTypes, ctor: ComponentCtor): ComponentIdentifier;
getIdForTypeMapping(typeMapping: ModelComponentTypes): string;
getCtorForType(typeMapping: ModelComponentTypes): Type<IComponent> | undefined;
getCtorFromId(id: string): Type<IComponent>;
getAllCtors(): Array<Type<IComponent>>;
getCtorForType(typeMapping: ModelComponentTypes): ComponentCtor | undefined;
getCtorFromId(id: string): ComponentCtor;
getAllCtors(): Array<ComponentCtor>;
getAllIds(): Array<string>;
}
class ComponentRegistry implements IComponentRegistry {
private _idToCtor: { [x: string]: Type<IComponent> } = {};
private _idToCtor: { [x: string]: ComponentCtor } = {};
private _typeNameToId: { [x: string]: string } = {};
registerComponentType(id: string, typeMapping: ModelComponentTypes, ctor: Type<IComponent>): string {
registerComponentType(id: string, typeMapping: ModelComponentTypes, ctor: ComponentCtor): string {
this._idToCtor[id] = ctor;
this._typeNameToId[ModelComponentTypes[typeMapping]] = id;
return id;
@@ -38,15 +40,15 @@ class ComponentRegistry implements IComponentRegistry {
return this._typeNameToId[ModelComponentTypes[typeMapping]];
}
public getCtorForType(typeMapping: ModelComponentTypes): Type<IComponent> | undefined {
public getCtorForType(typeMapping: ModelComponentTypes): ComponentCtor | undefined {
let id = this.getIdForTypeMapping(typeMapping);
return id ? this._idToCtor[id] : undefined;
}
public getCtorFromId(id: string): Type<IComponent> {
public getCtorFromId(id: string): ComponentCtor {
return this._idToCtor[id];
}
public getAllCtors(): Array<Type<IComponent>> {
public getAllCtors(): Array<ComponentCtor> {
return values(this._idToCtor);
}
@@ -59,6 +61,6 @@ class ComponentRegistry implements IComponentRegistry {
const componentRegistry = new ComponentRegistry();
platform.Registry.add(Extensions.ComponentContribution, componentRegistry);
export function registerComponentType(id: string, typeMapping: ModelComponentTypes, ctor: Type<IComponent>): ComponentIdentifier {
export function registerComponentType(id: string, typeMapping: ModelComponentTypes, ctor: ComponentCtor): ComponentIdentifier {
return componentRegistry.registerComponentType(id, typeMapping, ctor);
}

View File

@@ -1,232 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { FileBrowserTree } from 'sql/workbench/services/fileBrowser/common/fileBrowserTree';
import { FileNode } from 'sql/workbench/services/fileBrowser/common/fileNode';
import { IFileBrowserService } from 'sql/platform/fileBrowser/common/interfaces';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
import { Event, Emitter } from 'vs/base/common/event';
import Severity from 'vs/base/common/severity';
import { localize } from 'vs/nls';
import * as strings from 'vs/base/common/strings';
import { invalidProvider } from 'sql/base/common/errors';
export class FileBrowserService implements IFileBrowserService {
public _serviceBrand: undefined;
private _providers: { [handle: string]: azdata.FileBrowserProvider; } = Object.create(null);
private _onAddFileTree = new Emitter<FileBrowserTree>();
private _onExpandFolder = new Emitter<FileNode>();
private _onPathValidate = new Emitter<azdata.FileBrowserValidatedParams>();
private _pathToFileNodeMap: { [path: string]: FileNode } = {};
private _expandResolveMap: { [key: string]: any } = {};
static fileNodeId: number = 0;
constructor(@IConnectionManagementService private _connectionService: IConnectionManagementService,
@IErrorMessageService private _errorMessageService: IErrorMessageService) {
}
public registerProvider(providerId: string, provider: azdata.FileBrowserProvider): void {
this._providers[providerId] = provider;
}
public get onAddFileTree(): Event<FileBrowserTree> {
return this._onAddFileTree.event;
}
public get onExpandFolder(): Event<FileNode> {
return this._onExpandFolder.event;
}
public get onPathValidate(): Event<azdata.FileBrowserValidatedParams> {
return this._onPathValidate.event;
}
public openFileBrowser(ownerUri: string, expandPath: string, fileFilters: string[], changeFilter: boolean): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
const provider = this.getProvider(ownerUri);
if (provider) {
provider.openFileBrowser(ownerUri, expandPath, fileFilters, changeFilter).then(result => {
resolve(result);
}, error => {
reject(error);
});
} else {
reject(invalidProvider());
}
});
}
public onFileBrowserOpened(handle: number, fileBrowserOpenedParams: azdata.FileBrowserOpenedParams) {
if (fileBrowserOpenedParams.succeeded === true
&& fileBrowserOpenedParams.fileTree
&& fileBrowserOpenedParams.fileTree.rootNode
&& fileBrowserOpenedParams.fileTree.selectedNode
) {
let fileTree = this.convertFileTree(undefined, fileBrowserOpenedParams.fileTree.rootNode, fileBrowserOpenedParams.fileTree.selectedNode.fullPath, fileBrowserOpenedParams.ownerUri);
this._onAddFileTree.fire({ rootNode: fileTree.rootNode, selectedNode: fileTree.selectedNode, expandedNodes: fileTree.expandedNodes });
} else {
let genericErrorMessage = localize('fileBrowserErrorMessage', "An error occured while loading the file browser.");
let errorDialogTitle = localize('fileBrowserErrorDialogTitle', "File browser error");
let errorMessage = strings.isFalsyOrWhitespace(fileBrowserOpenedParams.message) ? genericErrorMessage : fileBrowserOpenedParams.message;
this._errorMessageService.showDialog(Severity.Error, errorDialogTitle, errorMessage);
}
}
public expandFolderNode(fileNode: FileNode): Promise<FileNode[]> {
this._pathToFileNodeMap[fileNode.fullPath] = fileNode;
let self = this;
return new Promise<FileNode[]>((resolve, reject) => {
const provider = this.getProvider(fileNode.ownerUri);
if (provider) {
provider.expandFolderNode(fileNode.ownerUri, fileNode.fullPath).then(result => {
let mapKey = self.generateResolveMapKey(fileNode.ownerUri, fileNode.fullPath);
self._expandResolveMap[mapKey] = resolve;
}, error => {
reject(error);
});
} else {
reject(invalidProvider());
}
});
}
public onFolderNodeExpanded(handle: number, fileBrowserExpandedParams: azdata.FileBrowserExpandedParams) {
let mapKey = this.generateResolveMapKey(fileBrowserExpandedParams.ownerUri, fileBrowserExpandedParams.expandPath);
let expandResolve = this._expandResolveMap[mapKey];
if (expandResolve) {
if (fileBrowserExpandedParams.succeeded === true) {
// get the expanded folder node
let expandedNode = this._pathToFileNodeMap[fileBrowserExpandedParams.expandPath];
if (expandedNode) {
if (fileBrowserExpandedParams.children && fileBrowserExpandedParams.children.length > 0) {
expandedNode.children = this.convertChildren(expandedNode, fileBrowserExpandedParams.children, fileBrowserExpandedParams.ownerUri);
}
expandResolve(expandedNode.children ? expandedNode.children : []);
this._onExpandFolder.fire(expandedNode);
} else {
expandResolve([]);
}
} else {
expandResolve([]);
}
}
}
public validateFilePaths(ownerUri: string, serviceType: string, selectedFiles: string[]): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
const provider = this.getProvider(ownerUri);
if (provider) {
provider.validateFilePaths(ownerUri, serviceType, selectedFiles).then(result => {
resolve(result);
}, error => {
reject(error);
});
} else {
reject(invalidProvider());
}
});
}
public onFilePathsValidated(handle: number, fileBrowserValidatedParams: azdata.FileBrowserValidatedParams) {
this._onPathValidate.fire(fileBrowserValidatedParams);
}
public closeFileBrowser(ownerUri: string): Promise<azdata.FileBrowserCloseResponse | undefined> {
let provider = this.getProvider(ownerUri);
if (provider) {
return Promise.resolve(provider.closeFileBrowser(ownerUri));
}
return Promise.resolve(undefined);
}
private generateResolveMapKey(ownerUri: string, expandPath: string): string {
return ownerUri + ':' + expandPath;
}
private getProvider(connectionUri: string): azdata.FileBrowserProvider | undefined {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
return this._providers[providerId];
} else {
return undefined;
}
}
private convertFileTree(parentNode: FileNode | undefined, fileTreeNode: azdata.FileTreeNode, expandPath: string, ownerUri: string): FileBrowserTree {
FileBrowserService.fileNodeId += 1;
let expandedNodes: FileNode[] = [];
let selectedNode: FileNode | undefined;
let fileNode = new FileNode(FileBrowserService.fileNodeId.toString(),
fileTreeNode.name,
fileTreeNode.fullPath,
fileTreeNode.isFile,
fileTreeNode.isExpanded,
ownerUri,
parentNode
);
if (fileNode.isExpanded === true) {
expandedNodes.push(fileNode);
}
if (fileTreeNode.children) {
let convertedChildren = [];
for (let i = 0; i < fileTreeNode.children.length; i++) {
let convertedFileTree: FileBrowserTree = this.convertFileTree(fileNode, fileTreeNode.children[i], expandPath, ownerUri);
convertedChildren.push(convertedFileTree.rootNode);
if (convertedFileTree.expandedNodes.length > 0) {
expandedNodes = expandedNodes.concat(convertedFileTree.expandedNodes);
}
if (convertedFileTree.selectedNode) {
selectedNode = convertedFileTree.selectedNode;
}
}
if (convertedChildren.length > 0) {
fileNode.children = convertedChildren;
}
}
if (!selectedNode && fileTreeNode.fullPath === expandPath) {
selectedNode = fileNode;
}
// Assume every folder has children initially
if (fileTreeNode.isFile === false) {
fileNode.hasChildren = true;
}
return { rootNode: fileNode, selectedNode: selectedNode, expandedNodes: expandedNodes };
}
private convertChildren(expandedNode: FileNode, childrenToConvert: azdata.FileTreeNode[], ownerUri: string): FileNode[] {
let childrenNodes = [];
for (let i = 0; i < childrenToConvert.length; i++) {
FileBrowserService.fileNodeId += 1;
let childNode = new FileNode(FileBrowserService.fileNodeId.toString(),
childrenToConvert[i].name,
childrenToConvert[i].fullPath,
childrenToConvert[i].isFile,
childrenToConvert[i].isExpanded,
ownerUri,
expandedNode
);
// Assume every folder has children initially
if (childrenToConvert[i].isFile === false) {
childNode.hasChildren = true;
}
childrenNodes.push(childNode);
}
return childrenNodes;
}
}

View File

@@ -1,58 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { FileBrowserTree } from 'sql/workbench/services/fileBrowser/common/fileBrowserTree';
import { FileNode } from 'sql/workbench/services/fileBrowser/common/fileNode';
import { Event } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const IFileBrowserService = createDecorator<IFileBrowserService>('fileBrowserService');
export interface IFileBrowserService {
_serviceBrand: undefined;
onAddFileTree: Event<FileBrowserTree>;
onExpandFolder: Event<FileNode>;
onPathValidate: Event<azdata.FileBrowserValidatedParams>;
/**
* Register file browser provider
*/
registerProvider(providerId: string, provider: azdata.FileBrowserProvider): void;
/**
* Open file browser
*/
openFileBrowser(ownerUri: string, expandPath: string, fileFilters: string[], changeFilter: boolean): Promise<boolean>;
/**
* Event called when file browser is opened
*/
onFileBrowserOpened(handle: number, fileBrowserOpenedParams: azdata.FileBrowserOpenedParams): void;
/**
* Expand folder node
*/
expandFolderNode(fileNode: FileNode): Promise<FileNode[]>;
/**
* Event called when children nodes are retrieved
*/
onFolderNodeExpanded(handle: number, fileBrowserExpandedParams: azdata.FileBrowserExpandedParams): void;
/**
* Validate selected file paths
*/
validateFilePaths(ownerUri: string, serviceType: string, selectedFiles: string[]): Promise<boolean>;
/**
* Event called when the validation is complete
*/
onFilePathsValidated(handle: number, fileBrowserValidatedParams: azdata.FileBrowserValidatedParams): void;
/**
* Close file browser
*/
closeFileBrowser(ownerUri: string): Promise<azdata.FileBrowserCloseResponse | undefined>;
}

View File

@@ -1,735 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Action } from 'vs/base/common/actions';
import * as nls from 'vs/nls';
import * as azdata from 'azdata';
import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
import { JobHistoryComponent } from 'sql/workbench/contrib/jobManagement/browser/jobHistory.component';
import { IJobManagementService } from '../common/interfaces';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { JobsViewComponent } from 'sql/workbench/contrib/jobManagement/browser/jobsView.component';
import { AlertsViewComponent } from 'sql/workbench/contrib/jobManagement/browser/alertsView.component';
import { OperatorsViewComponent } from 'sql/workbench/contrib/jobManagement/browser/operatorsView.component';
import { ProxiesViewComponent } from 'sql/workbench/contrib/jobManagement/browser/proxiesView.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
import { JobManagementView } from 'sql/workbench/contrib/jobManagement/browser/jobManagementView';
import { NotebooksViewComponent } from 'sql/workbench/contrib/jobManagement/browser/notebooksView.component';
export const successLabel: string = nls.localize('jobaction.successLabel', "Success");
export const errorLabel: string = nls.localize('jobaction.faillabel', "Error");
export enum JobActions {
Run = 'run',
Stop = 'stop'
}
export class IJobActionInfo {
ownerUri?: string;
targetObject?: any;
component: JobManagementView;
}
// Job actions
export class JobsRefreshAction extends Action {
public static ID = 'jobaction.refresh';
public static LABEL = nls.localize('jobaction.refresh', "Refresh");
constructor(
) {
super(JobsRefreshAction.ID, JobsRefreshAction.LABEL, 'refreshIcon');
}
public run(context: IJobActionInfo): Promise<boolean> {
return new Promise<boolean>((resolve, reject) => {
if (context) {
if (context.component) {
context.component.refreshJobs();
}
resolve(true);
} else {
reject(false);
}
});
}
}
export class NewJobAction extends Action {
public static ID = 'jobaction.newJob';
public static LABEL = nls.localize('jobaction.newJob', "New Job");
constructor(
) {
super(NewJobAction.ID, NewJobAction.LABEL, 'newStepIcon');
}
public run(context: IJobActionInfo): Promise<boolean> {
let component = context.component as JobsViewComponent;
return new Promise<boolean>(async (resolve, reject) => {
try {
await component.openCreateJobDialog();
resolve(true);
} catch (e) {
reject(e);
}
});
}
}
export class RunJobAction extends Action {
public static ID = 'jobaction.runJob';
public static LABEL = nls.localize('jobaction.run', "Run");
constructor(
@INotificationService private notificationService: INotificationService,
@IErrorMessageService private errorMessageService: IErrorMessageService,
@IJobManagementService private jobManagementService: IJobManagementService,
@IInstantiationService private instantationService: IInstantiationService,
@ITelemetryService private telemetryService: ITelemetryService
) {
super(RunJobAction.ID, RunJobAction.LABEL, 'start');
}
public run(context: IJobActionInfo): Promise<boolean> {
let jobName = context.targetObject.job.name;
let ownerUri = context.ownerUri;
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
this.telemetryService.publicLog(TelemetryKeys.RunAgentJob);
return new Promise<boolean>((resolve, reject) => {
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Run).then(async (result) => {
if (result.success) {
let startMsg = nls.localize('jobSuccessfullyStarted', ": The job was successfully started.");
this.notificationService.info(jobName + startMsg);
await refreshAction.run(context);
resolve(true);
} else {
this.errorMessageService.showDialog(Severity.Error, errorLabel, result.errorMessage);
resolve(false);
}
});
});
}
}
export class StopJobAction extends Action {
public static ID = 'jobaction.stopJob';
public static LABEL = nls.localize('jobaction.stop', "Stop");
constructor(
@INotificationService private notificationService: INotificationService,
@IErrorMessageService private errorMessageService: IErrorMessageService,
@IJobManagementService private jobManagementService: IJobManagementService,
@IInstantiationService private instantationService: IInstantiationService,
@ITelemetryService private telemetryService: ITelemetryService
) {
super(StopJobAction.ID, StopJobAction.LABEL, 'stop');
}
public run(context: IJobActionInfo): Promise<boolean> {
let jobName = context.targetObject.name;
let ownerUri = context.ownerUri;
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
this.telemetryService.publicLog(TelemetryKeys.StopAgentJob);
return new Promise<boolean>((resolve, reject) => {
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Stop).then(async (result) => {
if (result.success) {
await refreshAction.run(context);
let stopMsg = nls.localize('jobSuccessfullyStopped', ": The job was successfully stopped.");
this.notificationService.info(jobName + stopMsg);
resolve(true);
} else {
this.errorMessageService.showDialog(Severity.Error, 'Error', result.errorMessage);
resolve(false);
}
});
});
}
}
export class EditJobAction extends Action {
public static ID = 'jobaction.editJob';
public static LABEL = nls.localize('jobaction.editJob', "Edit Job");
constructor(
@ICommandService private _commandService: ICommandService
) {
super(EditJobAction.ID, EditJobAction.LABEL, 'edit');
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
this._commandService.executeCommand(
'agent.openJobDialog',
actionInfo.ownerUri,
actionInfo.targetObject.job);
return Promise.resolve(true);
}
}
export class OpenMaterializedNotebookAction extends Action {
public static ID = 'notebookAction.openNotebook';
public static LABEL = nls.localize('notebookAction.openNotebook', "Open");
constructor() {
super(OpenMaterializedNotebookAction.ID, OpenMaterializedNotebookAction.LABEL, 'openNotebook');
}
public run(context: any): Promise<boolean> {
context.component.openNotebook(context.history);
return Promise.resolve(true);
}
}
export class DeleteJobAction extends Action {
public static ID = 'jobaction.deleteJob';
public static LABEL = nls.localize('jobaction.deleteJob', "Delete Job");
constructor(
@INotificationService private _notificationService: INotificationService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IJobManagementService private _jobService: IJobManagementService,
@ITelemetryService private _telemetryService: ITelemetryService
) {
super(DeleteJobAction.ID, DeleteJobAction.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
let self = this;
let job = actionInfo.targetObject.job as azdata.AgentJobInfo;
self._notificationService.prompt(
Severity.Info,
nls.localize('jobaction.deleteJobConfirm', "Are you sure you'd like to delete the job '{0}'?", job.name),
[{
label: DeleteJobAction.LABEL,
run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentJob);
self._jobService.deleteJob(actionInfo.ownerUri, actionInfo.targetObject.job).then(result => {
if (!result || !result.success) {
let errorMessage = nls.localize("jobaction.failedToDeleteJob", "Could not delete job '{0}'.\nError: {1}",
job.name, result.errorMessage ? result.errorMessage : 'Unknown error');
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
} else {
let successMessage = nls.localize('jobaction.deletedJob', "The job was successfully deleted");
self._notificationService.info(successMessage);
}
});
}
}, {
label: DeleteAlertAction.CancelLabel,
run: () => { }
}]
);
return Promise.resolve(true);
}
}
// Step Actions
export class NewStepAction extends Action {
public static ID = 'jobaction.newStep';
public static LABEL = nls.localize('jobaction.newStep', "New Step");
constructor(
@ICommandService private _commandService: ICommandService
) {
super(NewStepAction.ID, NewStepAction.LABEL, 'newStepIcon');
}
public run(context: JobHistoryComponent): Promise<boolean> {
let ownerUri = context.ownerUri;
let server = context.serverName;
let jobInfo = context.agentJobInfo;
return new Promise<boolean>((resolve, reject) => {
resolve(this._commandService.executeCommand('agent.openNewStepDialog', ownerUri, server, jobInfo, null));
});
}
}
export class DeleteStepAction extends Action {
public static ID = 'jobaction.deleteStep';
public static LABEL = nls.localize('jobaction.deleteStep', "Delete Step");
constructor(
@INotificationService private _notificationService: INotificationService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IJobManagementService private _jobService: IJobManagementService,
@IInstantiationService private instantationService: IInstantiationService,
@ITelemetryService private _telemetryService: ITelemetryService
) {
super(DeleteStepAction.ID, DeleteStepAction.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
let self = this;
let step = actionInfo.targetObject as azdata.AgentJobStepInfo;
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
self._notificationService.prompt(
Severity.Info,
nls.localize('jobaction.deleteStepConfirm', "Are you sure you'd like to delete the step '{0}'?", step.stepName),
[{
label: DeleteStepAction.LABEL,
run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentJobStep);
self._jobService.deleteJobStep(actionInfo.ownerUri, actionInfo.targetObject).then(async (result) => {
if (!result || !result.success) {
let errorMessage = nls.localize('jobaction.failedToDeleteStep', "Could not delete step '{0}'.\nError: {1}",
step.stepName, result.errorMessage ? result.errorMessage : 'Unknown error');
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
await refreshAction.run(actionInfo);
} else {
let successMessage = nls.localize('jobaction.deletedStep', "The job step was successfully deleted");
self._notificationService.info(successMessage);
}
});
}
}, {
label: DeleteAlertAction.CancelLabel,
run: () => { }
}]
);
return Promise.resolve(true);
}
}
// Alert Actions
export class NewAlertAction extends Action {
public static ID = 'jobaction.newAlert';
public static LABEL = nls.localize('jobaction.newAlert', "New Alert");
constructor(
) {
super(NewAlertAction.ID, NewAlertAction.LABEL, 'newStepIcon');
}
public run(context: IJobActionInfo): Promise<boolean> {
let component = context.component as AlertsViewComponent;
return new Promise<boolean>((resolve, reject) => {
try {
component.openCreateAlertDialog();
resolve(true);
} catch (e) {
reject(e);
}
});
}
}
export class EditAlertAction extends Action {
public static ID = 'jobaction.editAlert';
public static LABEL = nls.localize('jobaction.editAlert', "Edit Alert");
constructor(
@ICommandService private _commandService: ICommandService
) {
super(EditAlertAction.ID, EditAlertAction.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
this._commandService.executeCommand(
'agent.openAlertDialog',
actionInfo.ownerUri,
actionInfo.targetObject.jobInfo,
actionInfo.targetObject.alertInfo);
return Promise.resolve(true);
}
}
export class DeleteAlertAction extends Action {
public static ID = 'jobaction.deleteAlert';
public static LABEL = nls.localize('jobaction.deleteAlert', "Delete Alert");
public static CancelLabel = nls.localize('jobaction.Cancel', "Cancel");
constructor(
@INotificationService private _notificationService: INotificationService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IJobManagementService private _jobService: IJobManagementService,
@ITelemetryService private _telemetryService: ITelemetryService
) {
super(DeleteAlertAction.ID, DeleteAlertAction.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
let self = this;
let alert = actionInfo.targetObject.alertInfo as azdata.AgentAlertInfo;
self._notificationService.prompt(
Severity.Info,
nls.localize('jobaction.deleteAlertConfirm', "Are you sure you'd like to delete the alert '{0}'?", alert.name),
[{
label: DeleteAlertAction.LABEL,
run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentAlert);
self._jobService.deleteAlert(actionInfo.ownerUri, alert).then(result => {
if (!result || !result.success) {
let errorMessage = nls.localize("jobaction.failedToDeleteAlert", "Could not delete alert '{0}'.\nError: {1}",
alert.name, result.errorMessage ? result.errorMessage : 'Unknown error');
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
} else {
let successMessage = nls.localize('jobaction.deletedAlert', "The alert was successfully deleted");
self._notificationService.info(successMessage);
}
});
}
}, {
label: DeleteAlertAction.CancelLabel,
run: () => { }
}]
);
return Promise.resolve(true);
}
}
// Operator Actions
export class NewOperatorAction extends Action {
public static ID = 'jobaction.newOperator';
public static LABEL = nls.localize('jobaction.newOperator', "New Operator");
constructor(
) {
super(NewOperatorAction.ID, NewOperatorAction.LABEL, 'newStepIcon');
}
public run(context: IJobActionInfo): Promise<boolean> {
let component = context.component as OperatorsViewComponent;
return new Promise<boolean>((resolve, reject) => {
try {
component.openCreateOperatorDialog();
resolve(true);
} catch (e) {
reject(e);
}
});
}
}
export class EditOperatorAction extends Action {
public static ID = 'jobaction.editAlert';
public static LABEL = nls.localize('jobaction.editOperator', "Edit Operator");
constructor(
@ICommandService private _commandService: ICommandService
) {
super(EditOperatorAction.ID, EditOperatorAction.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
this._commandService.executeCommand(
'agent.openOperatorDialog',
actionInfo.ownerUri,
actionInfo.targetObject);
return Promise.resolve(true);
}
}
export class DeleteOperatorAction extends Action {
public static ID = 'jobaction.deleteOperator';
public static LABEL = nls.localize('jobaction.deleteOperator', "Delete Operator");
constructor(
@INotificationService private _notificationService: INotificationService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IJobManagementService private _jobService: IJobManagementService,
@ITelemetryService private _telemetryService: ITelemetryService
) {
super(DeleteOperatorAction.ID, DeleteOperatorAction.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
const self = this;
let operator = actionInfo.targetObject as azdata.AgentOperatorInfo;
self._notificationService.prompt(
Severity.Info,
nls.localize('jobaction.deleteOperatorConfirm', "Are you sure you'd like to delete the operator '{0}'?", operator.name),
[{
label: DeleteOperatorAction.LABEL,
run: () => {
self._telemetryService.publicLog(TelemetryKeys.DeleteAgentOperator);
self._jobService.deleteOperator(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
if (!result || !result.success) {
let errorMessage = nls.localize("jobaction.failedToDeleteOperator", "Could not delete operator '{0}'.\nError: {1}",
operator.name, result.errorMessage ? result.errorMessage : 'Unknown error');
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
} else {
let successMessage = nls.localize('joaction.deletedOperator', "The operator was deleted successfully");
self._notificationService.info(successMessage);
}
});
}
}, {
label: DeleteAlertAction.CancelLabel,
run: () => { }
}]
);
return Promise.resolve(true);
}
}
// Proxy Actions
export class NewProxyAction extends Action {
public static ID = 'jobaction.newProxy';
public static LABEL = nls.localize('jobaction.newProxy', "New Proxy");
constructor(
) {
super(NewProxyAction.ID, NewProxyAction.LABEL, 'newStepIcon');
}
public run(context: IJobActionInfo): Promise<boolean> {
let component = context.component as ProxiesViewComponent;
return new Promise<boolean>((resolve, reject) => {
try {
component.openCreateProxyDialog();
resolve(true);
} catch (e) {
reject(e);
}
});
}
}
export class EditProxyAction extends Action {
public static ID = 'jobaction.editProxy';
public static LABEL = nls.localize('jobaction.editProxy', "Edit Proxy");
constructor(
@ICommandService private _commandService: ICommandService,
@IJobManagementService private _jobManagementService: IJobManagementService
) {
super(EditProxyAction.ID, EditProxyAction.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
return Promise.resolve(this._jobManagementService.getCredentials(actionInfo.ownerUri).then((result) => {
if (result && result.credentials) {
this._commandService.executeCommand(
'agent.openProxyDialog',
actionInfo.ownerUri,
actionInfo.targetObject,
result.credentials);
return true;
} else {
return false;
}
}));
}
}
export class DeleteProxyAction extends Action {
public static ID = 'jobaction.deleteProxy';
public static LABEL = nls.localize('jobaction.deleteProxy', "Delete Proxy");
constructor(
@INotificationService private _notificationService: INotificationService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IJobManagementService private _jobService: IJobManagementService,
@ITelemetryService private _telemetryService: ITelemetryService
) {
super(DeleteProxyAction.ID, DeleteProxyAction.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
let self = this;
let proxy = actionInfo.targetObject as azdata.AgentProxyInfo;
self._notificationService.prompt(
Severity.Info,
nls.localize('jobaction.deleteProxyConfirm', "Are you sure you'd like to delete the proxy '{0}'?", proxy.accountName),
[{
label: DeleteProxyAction.LABEL,
run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentProxy);
self._jobService.deleteProxy(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
if (!result || !result.success) {
let errorMessage = nls.localize("jobaction.failedToDeleteProxy", "Could not delete proxy '{0}'.\nError: {1}",
proxy.accountName, result.errorMessage ? result.errorMessage : 'Unknown error');
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
} else {
let successMessage = nls.localize('jobaction.deletedProxy', "The proxy was deleted successfully");
self._notificationService.info(successMessage);
}
});
}
}, {
label: DeleteAlertAction.CancelLabel,
run: () => { }
}]
);
return Promise.resolve(true);
}
}
//Notebook Actions
export class NewNotebookJobAction extends Action {
public static ID = 'notebookaction.newJob';
public static LABEL = nls.localize('notebookaction.newJob', "New Notebook Job");
constructor(
) {
super(NewNotebookJobAction.ID, NewNotebookJobAction.LABEL, 'newStepIcon');
}
public async run(context: IJobActionInfo): Promise<boolean> {
let component = context.component as NotebooksViewComponent;
await component.openCreateNotebookDialog();
return true;
}
}
export class EditNotebookJobAction extends Action {
public static ID = 'notebookaction.editNotebook';
public static LABEL = nls.localize('notebookaction.editJob', "Edit");
constructor(
@ICommandService private _commandService: ICommandService
) {
super(EditNotebookJobAction.ID, EditNotebookJobAction.LABEL, 'edit');
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
this._commandService.executeCommand(
'agent.openNotebookDialog',
actionInfo.ownerUri,
actionInfo.targetObject.job);
return Promise.resolve(true);
}
}
export class OpenTemplateNotebookAction extends Action {
public static ID = 'notebookaction.openTemplate';
public static LABEL = nls.localize('notebookaction.openNotebook', "Open Template Notebook");
constructor() {
super(OpenTemplateNotebookAction.ID, OpenTemplateNotebookAction.LABEL, 'opennotebook');
}
public run(actionInfo: any): Promise<boolean> {
actionInfo.component.openTemplateNotebook();
return Promise.resolve(true);
}
}
export class DeleteNotebookAction extends Action {
public static ID = 'notebookaction.deleteNotebook';
public static LABEL = nls.localize('notebookaction.deleteNotebook', "Delete");
constructor(
@INotificationService private _notificationService: INotificationService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IJobManagementService private _jobService: IJobManagementService,
@IInstantiationService private instantationService: IInstantiationService,
@ITelemetryService private _telemetryService: ITelemetryService
) {
super(DeleteNotebookAction.ID, DeleteNotebookAction.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
let self = this;
let notebook = actionInfo.targetObject.job as azdata.AgentNotebookInfo;
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
self._notificationService.prompt(
Severity.Info,
nls.localize('jobaction.deleteNotebookConfirm', "Are you sure you'd like to delete the notebook '{0}'?", notebook.name),
[{
label: DeleteNotebookAction.LABEL,
run: () => {
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentJob);
self._jobService.deleteNotebook(actionInfo.ownerUri, actionInfo.targetObject.job).then(async (result) => {
if (!result || !result.success) {
await refreshAction.run(actionInfo);
let errorMessage = nls.localize("jobaction.failedToDeleteNotebook", "Could not delete notebook '{0}'.\nError: {1}",
notebook.name, result.errorMessage ? result.errorMessage : 'Unknown error');
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
} else {
let successMessage = nls.localize('jobaction.deletedNotebook', "The notebook was successfully deleted");
self._notificationService.info(successMessage);
}
});
}
}, {
label: DeleteAlertAction.CancelLabel,
run: () => { }
}]
);
return Promise.resolve(true);
}
}
export class PinNotebookMaterializedAction extends Action {
public static ID = 'notebookaction.openTemplate';
public static LABEL = nls.localize('notebookaction.pinNotebook', "Pin");
constructor() {
super(PinNotebookMaterializedAction.ID, PinNotebookMaterializedAction.LABEL);
}
public run(actionInfo: any): Promise<boolean> {
actionInfo.component.toggleNotebookPin(actionInfo.history, true);
return Promise.resolve(true);
}
}
export class DeleteMaterializedNotebookAction extends Action {
public static ID = 'notebookaction.deleteMaterializedNotebook';
public static LABEL = nls.localize('notebookaction.deleteMaterializedNotebook', "Delete");
constructor() {
super(DeleteMaterializedNotebookAction.ID, DeleteMaterializedNotebookAction.LABEL);
}
public run(actionInfo: any): Promise<boolean> {
actionInfo.component.deleteMaterializedNotebook(actionInfo.history);
return Promise.resolve(true);
}
}
export class UnpinNotebookMaterializedAction extends Action {
public static ID = 'notebookaction.unpinNotebook';
public static LABEL = nls.localize('notebookaction.unpinNotebook', "Unpin");
constructor() {
super(UnpinNotebookMaterializedAction.ID, UnpinNotebookMaterializedAction.LABEL);
}
public run(actionInfo: any): Promise<boolean> {
actionInfo.component.toggleNotebookPin(actionInfo.history, false);
return Promise.resolve(true);
}
}
export class RenameNotebookMaterializedAction extends Action {
public static ID = 'notebookaction.openTemplate';
public static LABEL = nls.localize('notebookaction.renameNotebook', "Rename");
constructor() {
super(RenameNotebookMaterializedAction.ID, RenameNotebookMaterializedAction.LABEL);
}
public run(actionInfo: any): Promise<boolean> {
actionInfo.component.renameNotebook(actionInfo.history);
return Promise.resolve(true);
}
}
export class OpenLatestRunMaterializedNotebook extends Action {
public static ID = 'notebookaction.openLatestRun';
public static LABEL = nls.localize('notebookaction.openLatestRun', "Open Latest Run");
constructor() {
super(OpenLatestRunMaterializedNotebook.ID, OpenLatestRunMaterializedNotebook.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
actionInfo.component.openLastNRun(actionInfo.targetObject.job, 0, 1);
return Promise.resolve(true);
}
}

View File

@@ -1,150 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { find } from 'vs/base/common/arrays';
export class JobManagementUtilities {
public static startIconClass: string = 'action-label codicon runJobIcon';
public static stopIconClass: string = 'action-label codicon stopJobIcon';
public static jobMessageLength: number = 110;
public static convertToStatusString(status: number): string {
switch (status) {
case (0): return nls.localize('agentUtilities.failed', "Failed");
case (1): return nls.localize('agentUtilities.succeeded', "Succeeded");
case (2): return nls.localize('agentUtilities.retry', "Retry");
case (3): return nls.localize('agentUtilities.canceled', "Cancelled");
case (4): return nls.localize('agentUtilities.inProgress', "In Progress");
case (5): return nls.localize('agentUtilities.statusUnknown', "Status Unknown");
default: return nls.localize('agentUtilities.statusUnknown', "Status Unknown");
}
}
public static convertToExecutionStatusString(status: number): string {
switch (status) {
case (1): return nls.localize('agentUtilities.executing', "Executing");
case (2): return nls.localize('agentUtilities.waitingForThread', "Waiting for Thread");
case (3): return nls.localize('agentUtilities.betweenRetries', "Between Retries");
case (4): return nls.localize('agentUtilities.idle', "Idle");
case (5): return nls.localize('agentUtilities.suspended', "Suspended");
case (6): return nls.localize('agentUtilities.obsolete', "[Obsolete]");
case (7): return 'PerformingCompletionActions';
default: return nls.localize('agentUtilities.statusUnknown', "Status Unknown");
}
}
public static convertToResponse(bool: boolean) {
return bool ? nls.localize('agentUtilities.yes', "Yes") : nls.localize('agentUtilities.no', "No");
}
public static convertToNextRun(date: string) {
if (find(date, x => x === '1/1/0001')) {
return nls.localize('agentUtilities.notScheduled', "Not Scheduled");
} else {
return date;
}
}
public static convertToLastRun(date: string) {
if (find(date, x => x === '1/1/0001')) {
return nls.localize('agentUtilities.neverRun', "Never Run");
} else {
return date;
}
}
public static setRunnable(icon: HTMLElement, index: number) {
if (find(icon.className, x => x === 'non-runnable')) {
icon.className = icon.className.slice(0, index);
}
}
public static getActionIconClassName(startIcon: HTMLElement, stopIcon: HTMLElement, executionStatus: number) {
this.setRunnable(startIcon, JobManagementUtilities.startIconClass.length);
this.setRunnable(stopIcon, JobManagementUtilities.stopIconClass.length);
switch (executionStatus) {
case (1): // executing
startIcon.className += ' non-runnable';
return;
case (2): // Waiting for thread
startIcon.className += ' non-runnable';
return;
case (3): // Between retries
startIcon.className += ' non-runnable';
return;
case (4): //Idle
stopIcon.className += ' non-runnable';
return;
case (5): // Suspended
stopIcon.className += ' non-runnable';
return;
case (6): //obsolete
startIcon.className += ' non-runnable';
stopIcon.className += ' non-runnable';
return;
case (7): //Performing Completion Actions
startIcon.className += ' non-runnable';
return;
default:
return;
}
}
public static convertDurationToSeconds(duration: string): number {
let split = duration.split(':');
let seconds = (+split[0]) * 60 * 60 + (+split[1]) * 60 + (+split[2]);
return seconds;
}
public static convertColFieldToName(colField: string) {
switch (colField) {
case ('name'):
return 'Name';
case ('lastRun'):
return 'Last Run';
case ('nextRun'):
return 'Next Run';
case ('enabled'):
return 'Enabled';
case ('status'):
return 'Status';
case ('category'):
return 'Category';
case ('runnable'):
return 'Runnable';
case ('schedule'):
return 'Schedule';
case ('lastRunOutcome'):
return 'Last Run Outcome';
}
return '';
}
public static convertColNameToField(columnName: string) {
switch (columnName) {
case ('Name'):
return 'name';
case ('Last Run'):
return 'lastRun';
case ('Next Run'):
return 'nextRun';
case ('Enabled'):
return 'enabled';
case ('Status'):
return 'status';
case ('Category'):
return 'category';
case ('Runnable'):
return 'runnable';
case ('Schedule'):
return 'schedule';
case ('Last Run Outcome'):
return 'lastRunOutcome';
}
return '';
}
}

View File

@@ -1,56 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { JobCacheObject, AlertsCacheObject, ProxiesCacheObject, OperatorsCacheObject, NotebookCacheObject } from './jobManagementService';
import { Event } from 'vs/base/common/event';
export const SERVICE_ID = 'jobManagementService';
export const IJobManagementService = createDecorator<IJobManagementService>(SERVICE_ID);
export interface IJobManagementService {
_serviceBrand: undefined;
onDidChange: Event<void>;
registerProvider(providerId: string, provider: azdata.AgentServicesProvider): void;
fireOnDidChange(): void;
getJobs(connectionUri: string): Thenable<azdata.AgentJobsResult>;
getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable<azdata.AgentJobHistoryResult>;
deleteJob(connectionUri: string, job: azdata.AgentJobInfo): Thenable<azdata.ResultStatus>;
deleteJobStep(connectionUri: string, step: azdata.AgentJobStepInfo): Thenable<azdata.ResultStatus>;
getNotebooks(connectionUri: string): Thenable<azdata.AgentNotebooksResult>;
getNotebookHistory(connectionUri: string, jobId: string, jobName: string, targetDatabase: string): Thenable<azdata.AgentNotebookHistoryResult>;
getMaterialziedNotebook(connectionUri: string, targetDatabase: string, notebookMaterializedId: number): Thenable<azdata.AgentNotebookMaterializedResult>;
getTemplateNotebook(connectionUri: string, targetDatabase: string, jobId: string): Thenable<azdata.AgentNotebookTemplateResult>;
deleteNotebook(connectionUri: string, notebook: azdata.AgentNotebookInfo): Thenable<azdata.ResultStatus>;
deleteMaterializedNotebook(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string): Thenable<azdata.ResultStatus>;
updateNotebookMaterializedName(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, name: string);
updateNotebookMaterializedPin(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, pin: boolean);
getAlerts(connectionUri: string): Thenable<azdata.AgentAlertsResult>;
deleteAlert(connectionUri: string, alert: azdata.AgentAlertInfo): Thenable<azdata.ResultStatus>;
getOperators(connectionUri: string): Thenable<azdata.AgentOperatorsResult>;
deleteOperator(connectionUri: string, operator: azdata.AgentOperatorInfo): Thenable<azdata.ResultStatus>;
getProxies(connectionUri: string): Thenable<azdata.AgentProxiesResult>;
deleteProxy(connectionUri: string, proxy: azdata.AgentProxyInfo): Thenable<azdata.ResultStatus>;
getCredentials(connectionUri: string): Thenable<azdata.GetCredentialsResult>;
jobAction(connectionUri: string, jobName: string, action: string): Thenable<azdata.ResultStatus>;
addToCache(server: string, cache: JobCacheObject | OperatorsCacheObject | NotebookCacheObject);
jobCacheObjectMap: { [server: string]: JobCacheObject; };
notebookCacheObjectMap: { [server: string]: NotebookCacheObject; };
operatorsCacheObjectMap: { [server: string]: OperatorsCacheObject; };
alertsCacheObjectMap: { [server: string]: AlertsCacheObject; };
proxiesCacheObjectMap: { [server: string]: ProxiesCacheObject };
addToCache(server: string, cache: JobCacheObject | ProxiesCacheObject | AlertsCacheObject | OperatorsCacheObject | NotebookCacheObject);
}

View File

@@ -1,509 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import * as azdata from 'azdata';
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { Event, Emitter } from 'vs/base/common/event';
export class JobManagementService implements IJobManagementService {
_serviceBrand: undefined;
private _onDidChange = new Emitter<void>();
public readonly onDidChange: Event<void> = this._onDidChange.event;
private _providers: { [handle: string]: azdata.AgentServicesProvider; } = Object.create(null);
private _jobCacheObjectMap: { [server: string]: JobCacheObject; } = {};
private _operatorsCacheObjectMap: { [server: string]: OperatorsCacheObject; } = {};
private _alertsCacheObject: { [server: string]: AlertsCacheObject; } = {};
private _proxiesCacheObjectMap: { [server: string]: ProxiesCacheObject; } = {};
private _notebookCacheObjectMap: { [server: string]: NotebookCacheObject; } = {};
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService
) {
}
public fireOnDidChange(): void {
this._onDidChange.fire(void 0);
}
// Jobs
public getJobs(connectionUri: string): Thenable<azdata.AgentJobsResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getJobs(connectionUri);
});
}
public deleteJob(connectionUri: string, job: azdata.AgentJobInfo): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.deleteJob(connectionUri, job);
});
}
public getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable<azdata.AgentJobHistoryResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getJobHistory(connectionUri, jobID, jobName);
});
}
public jobAction(connectionUri: string, jobName: string, action: string): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.jobAction(connectionUri, jobName, action);
});
}
// Steps
public deleteJobStep(connectionUri: string, stepInfo: azdata.AgentJobStepInfo): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.deleteJobStep(connectionUri, stepInfo);
});
}
// Notebooks
public getNotebooks(connectionUri: string): Thenable<azdata.AgentNotebooksResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getNotebooks(connectionUri);
});
}
public getNotebookHistory(connectionUri: string, jobID: string, jobName: string, targetDatabase: string): Thenable<azdata.AgentNotebookHistoryResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getNotebookHistory(connectionUri, jobID, jobName, targetDatabase);
});
}
public getMaterialziedNotebook(connectionUri: string, targetDatabase: string, notebookMaterializedId: number): Thenable<azdata.AgentNotebookMaterializedResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getMaterializedNotebook(connectionUri, targetDatabase, notebookMaterializedId);
});
}
public getTemplateNotebook(connectionUri: string, targetDatabase: string, jobId: string): Thenable<azdata.AgentNotebookTemplateResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getTemplateNotebook(connectionUri, targetDatabase, jobId);
});
}
public deleteNotebook(connectionUri: string, notebook: azdata.AgentNotebookInfo): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.deleteNotebook(connectionUri, notebook);
});
}
public deleteMaterializedNotebook(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.deleteMaterializedNotebook(connectionUri, agentNotebookHistory, targetDatabase);
});
}
public updateNotebookMaterializedName(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, name: string): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.updateNotebookMaterializedName(connectionUri, agentNotebookHistory, targetDatabase, name);
});
}
public updateNotebookMaterializedPin(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, pin: boolean): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.updateNotebookMaterializedPin(connectionUri, agentNotebookHistory, targetDatabase, pin);
});
}
// Alerts
public getAlerts(connectionUri: string): Thenable<azdata.AgentAlertsResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getAlerts(connectionUri);
});
}
public deleteAlert(connectionUri: string, alert: azdata.AgentAlertInfo): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.deleteAlert(connectionUri, alert);
});
}
// Operators
public getOperators(connectionUri: string): Thenable<azdata.AgentOperatorsResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getOperators(connectionUri);
});
}
public deleteOperator(connectionUri: string, operator: azdata.AgentOperatorInfo): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.deleteOperator(connectionUri, operator);
});
}
// Proxies
public getProxies(connectionUri: string): Thenable<azdata.AgentProxiesResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getProxies(connectionUri);
});
}
public deleteProxy(connectionUri: string, proxy: azdata.AgentProxyInfo): Thenable<azdata.ResultStatus> {
return this._runAction(connectionUri, (runner) => {
return runner.deleteProxy(connectionUri, proxy);
});
}
public getCredentials(connectionUri: string): Thenable<azdata.GetCredentialsResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getCredentials(connectionUri);
});
}
private _runAction<T>(uri: string, action: (handler: azdata.AgentServicesProvider) => Thenable<T>): Thenable<T> {
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
if (!providerId) {
return Promise.reject(new Error(localize('providerIdNotValidError', "Connection is required in order to interact with JobManagementService")));
}
let handler = this._providers[providerId];
if (handler) {
return action(handler);
} else {
return Promise.reject(new Error(localize('noHandlerRegistered', "No Handler Registered")));
}
}
public registerProvider(providerId: string, provider: azdata.AgentServicesProvider): void {
this._providers[providerId] = provider;
}
public get jobCacheObjectMap(): { [server: string]: JobCacheObject; } {
return this._jobCacheObjectMap;
}
public get alertsCacheObjectMap(): { [server: string]: AlertsCacheObject; } {
return this._alertsCacheObject;
}
public get notebookCacheObjectMap(): { [server: string]: NotebookCacheObject; } {
return this._notebookCacheObjectMap;
}
public get proxiesCacheObjectMap(): { [server: string]: ProxiesCacheObject; } {
return this._proxiesCacheObjectMap;
}
public get operatorsCacheObjectMap(): { [server: string]: OperatorsCacheObject } {
return this._operatorsCacheObjectMap;
}
public addToCache(server: string, cacheObject: JobCacheObject | OperatorsCacheObject | ProxiesCacheObject | AlertsCacheObject | NotebookCacheObject) {
if (cacheObject instanceof JobCacheObject) {
this._jobCacheObjectMap[server] = cacheObject;
} else if (cacheObject instanceof OperatorsCacheObject) {
this._operatorsCacheObjectMap[server] = cacheObject;
} else if (cacheObject instanceof AlertsCacheObject) {
this._alertsCacheObject[server] = cacheObject;
} else if (cacheObject instanceof ProxiesCacheObject) {
this._proxiesCacheObjectMap[server] = cacheObject;
} else if (cacheObject instanceof NotebookCacheObject) {
this._notebookCacheObjectMap[server] = cacheObject;
}
}
}
/**
* Server level caching of jobs/job histories and their views
*/
export class JobCacheObject {
_serviceBrand: undefined;
private _jobs: azdata.AgentJobInfo[] = [];
private _jobHistories: { [jobID: string]: azdata.AgentJobHistoryInfo[]; } = {};
private _jobSteps: { [jobID: string]: azdata.AgentJobStepInfo[]; } = {};
private _jobAlerts: { [jobID: string]: azdata.AgentAlertInfo[]; } = {};
private _jobSchedules: { [jobID: string]: azdata.AgentJobScheduleInfo[]; } = {};
private _runCharts: { [jobID: string]: string[]; } = {};
private _prevJobID: string;
private _serverName: string;
private _dataView: Slick.Data.DataView<any>;
/* Getters */
public get jobs(): azdata.AgentJobInfo[] {
return this._jobs;
}
public get jobHistories(): { [jobID: string]: azdata.AgentJobHistoryInfo[] } {
return this._jobHistories;
}
public get prevJobID(): string {
return this._prevJobID;
}
public getJobHistory(jobID: string): azdata.AgentJobHistoryInfo[] {
return this._jobHistories[jobID];
}
public get serverName(): string {
return this._serverName;
}
public get dataView(): Slick.Data.DataView<any> {
return this._dataView;
}
public getRunChart(jobID: string): string[] {
return this._runCharts[jobID];
}
public getJobSteps(jobID: string): azdata.AgentJobStepInfo[] {
return this._jobSteps[jobID];
}
public getJobAlerts(jobID: string): azdata.AgentAlertInfo[] {
return this._jobAlerts[jobID];
}
public getJobSchedules(jobID: string): azdata.AgentJobScheduleInfo[] {
return this._jobSchedules[jobID];
}
/* Setters */
public set jobs(value: azdata.AgentJobInfo[]) {
this._jobs = value;
}
public set jobHistories(value: { [jobID: string]: azdata.AgentJobHistoryInfo[]; }) {
this._jobHistories = value;
}
public set prevJobID(value: string) {
this._prevJobID = value;
}
public setJobHistory(jobID: string, value: azdata.AgentJobHistoryInfo[]) {
this._jobHistories[jobID] = value;
}
public setRunChart(jobID: string, value: string[]) {
this._runCharts[jobID] = value;
}
public set serverName(value: string) {
this._serverName = value;
}
public set dataView(value: Slick.Data.DataView<any>) {
this._dataView = value;
}
public setJobSteps(jobID: string, value: azdata.AgentJobStepInfo[]) {
this._jobSteps[jobID] = value;
}
public setJobAlerts(jobID: string, value: azdata.AgentAlertInfo[]) {
this._jobAlerts[jobID] = value;
}
public setJobSchedules(jobID: string, value: azdata.AgentJobScheduleInfo[]) {
this._jobSchedules[jobID] = value;
}
}
/**
* Server level caching of Operators
*/
export class NotebookCacheObject {
_serviceBrand: any;
private _notebooks: azdata.AgentNotebookInfo[] = [];
private _notebookHistories: { [jobID: string]: azdata.AgentNotebookHistoryInfo[]; } = {};
private _jobSteps: { [jobID: string]: azdata.AgentJobStepInfo[]; } = {};
private _jobSchedules: { [jobID: string]: azdata.AgentJobScheduleInfo[]; } = {};
private _runCharts: { [jobID: string]: string[]; } = {};
private _prevJobID: string;
private _serverName: string;
private _dataView: Slick.Data.DataView<any>;
/* Getters */
public get notebooks(): azdata.AgentNotebookInfo[] {
return this._notebooks;
}
public get notebookHistories(): { [jobID: string]: azdata.AgentNotebookHistoryInfo[] } {
return this._notebookHistories;
}
public get prevJobID(): string {
return this._prevJobID;
}
public getNotebookHistory(jobID: string): azdata.AgentNotebookHistoryInfo[] {
return this._notebookHistories[jobID];
}
public get serverName(): string {
return this._serverName;
}
public get dataView(): Slick.Data.DataView<any> {
return this._dataView;
}
public getRunChart(jobID: string): string[] {
return this._runCharts[jobID];
}
public getJobSteps(jobID: string): azdata.AgentJobStepInfo[] {
return this._jobSteps[jobID];
}
public getJobSchedules(jobID: string): azdata.AgentJobScheduleInfo[] {
return this._jobSchedules[jobID];
}
/* Setters */
public set notebooks(value: azdata.AgentNotebookInfo[]) {
this._notebooks = value;
}
public set notebookHistories(value: { [jobID: string]: azdata.AgentNotebookHistoryInfo[]; }) {
this._notebookHistories = value;
}
public set prevJobID(value: string) {
this._prevJobID = value;
}
public setNotebookHistory(jobID: string, value: azdata.AgentNotebookHistoryInfo[]) {
this._notebookHistories[jobID] = value;
}
public setRunChart(jobID: string, value: string[]) {
this._runCharts[jobID] = value;
}
public set serverName(value: string) {
this._serverName = value;
}
public set dataView(value: Slick.Data.DataView<any>) {
this._dataView = value;
}
public setJobSteps(jobID: string, value: azdata.AgentJobStepInfo[]) {
this._jobSteps[jobID] = value;
}
public setJobSchedules(jobID: string, value: azdata.AgentJobScheduleInfo[]) {
this._jobSchedules[jobID] = value;
}
}
/**
* Server level caching of Operators
*/
export class OperatorsCacheObject {
_serviceBrand: undefined;
private _operators: azdata.AgentOperatorInfo[];
private _dataView: Slick.Data.DataView<any>;
private _serverName: string;
/** Getters */
public get operators(): azdata.AgentOperatorInfo[] {
return this._operators;
}
public get dataview(): Slick.Data.DataView<any> {
return this._dataView;
}
public get serverName(): string {
return this._serverName;
}
/** Setters */
public set operators(value: azdata.AgentOperatorInfo[]) {
this._operators = value;
}
public set dataview(value: Slick.Data.DataView<any>) {
this._dataView = value;
}
public set serverName(value: string) {
this._serverName = value;
}
}
/*
* Server level caching of job alerts and the alerts view
*/
export class AlertsCacheObject {
_serviceBrand: undefined;
private _alerts: azdata.AgentAlertInfo[];
private _dataView: Slick.Data.DataView<any>;
private _serverName: string;
/** Getters */
public get alerts(): azdata.AgentAlertInfo[] {
return this._alerts;
}
public get dataview(): Slick.Data.DataView<any> {
return this._dataView;
}
public get serverName(): string {
return this._serverName;
}
/** Setters */
public set alerts(value: azdata.AgentAlertInfo[]) {
this._alerts = value;
}
public set dataview(value: Slick.Data.DataView<any>) {
this._dataView = value;
}
public set serverName(value: string) {
this._serverName = value;
}
}
/**
* Server level caching of job proxies and proxies view
*/
export class ProxiesCacheObject {
_serviceBrand: undefined;
private _proxies: azdata.AgentProxyInfo[];
private _dataView: Slick.Data.DataView<any>;
private _serverName: string;
/**
* Getters
*/
public get proxies(): azdata.AgentProxyInfo[] {
return this._proxies;
}
public get dataview(): Slick.Data.DataView<any> {
return this._dataView;
}
public get serverName(): string {
return this._serverName;
}
/** Setters */
public set proxies(value: azdata.AgentProxyInfo[]) {
this._proxies = value;
}
public set dataview(value: Slick.Data.DataView<any>) {
this._dataView = value;
}
public set serverName(value: string) {
this._serverName = value;
}
}

View File

@@ -1,19 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { JobManagementService } from 'sql/platform/jobManagement/common/jobManagementService';
import * as assert from 'assert';
// TESTS ///////////////////////////////////////////////////////////////////
suite('Job Management service tests', () => {
setup(() => {
});
test('Construction - Job Service Initialization', () => {
// ... Create instance of the service and reder account picker
let service = new JobManagementService(undefined);
assert(service);
});
});

View File

@@ -1,363 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as TypeMoq from 'typemoq';
import * as assert from 'assert';
import { JobsRefreshAction, NewJobAction, EditJobAction, RunJobAction, StopJobAction, DeleteJobAction, NewStepAction, DeleteStepAction, NewAlertAction, EditAlertAction, DeleteAlertAction, NewOperatorAction, EditOperatorAction, DeleteOperatorAction, NewProxyAction, EditProxyAction, DeleteProxyAction } from 'sql/platform/jobManagement/browser/jobActions';
import { JobManagementService } from 'sql/platform/jobManagement/common/jobManagementService';
// Mock View Components
let mockJobsViewComponent: TypeMoq.Mock<TestJobManagementView>;
let mockAlertsViewComponent: TypeMoq.Mock<TestJobManagementView>;
let mockOperatorsViewComponent: TypeMoq.Mock<TestJobManagementView>;
let mockProxiesViewComponent: TypeMoq.Mock<TestJobManagementView>;
let mockJobManagementService: TypeMoq.Mock<JobManagementService>;
// Mock Job Actions
let mockRefreshAction: TypeMoq.Mock<JobsRefreshAction>;
let mockNewJobAction: TypeMoq.Mock<NewJobAction>;
let mockEditJobAction: TypeMoq.Mock<EditJobAction>;
let mockRunJobAction: TypeMoq.Mock<RunJobAction>;
let mockStopJobAction: TypeMoq.Mock<StopJobAction>;
let mockDeleteJobAction: TypeMoq.Mock<DeleteJobAction>;
// Mock Step Actions
let mockNewStepAction: TypeMoq.Mock<NewStepAction>;
let mockDeleteStepAction: TypeMoq.Mock<DeleteStepAction>;
// Mock Alert Actions
let mockNewAlertAction: TypeMoq.Mock<NewAlertAction>;
let mockEditAlertAction: TypeMoq.Mock<EditAlertAction>;
let mockDeleteAlertAction: TypeMoq.Mock<DeleteAlertAction>;
// Mock Operator Actions
let mockNewOperatorAction: TypeMoq.Mock<NewOperatorAction>;
let mockEditOperatorAction: TypeMoq.Mock<EditOperatorAction>;
let mockDeleteOperatorAction: TypeMoq.Mock<DeleteOperatorAction>;
// Mock Proxy Actions
let mockNewProxyAction: TypeMoq.Mock<NewProxyAction>;
let mockEditProxyAction: TypeMoq.Mock<EditProxyAction>;
let mockDeleteProxyAction: TypeMoq.Mock<DeleteProxyAction>;
/**
* Class to test Job Management Views
*/
class TestJobManagementView {
refreshJobs() { return undefined; }
openCreateJobDialog() { return undefined; }
openCreateAlertDialog() { return undefined; }
openCreateOperatorDialog() { return undefined; }
openCreateProxyDialog() { return undefined; }
}
// Tests
suite('Job Management Actions', () => {
// Job Actions
setup(() => {
mockJobsViewComponent = TypeMoq.Mock.ofType<TestJobManagementView>(TestJobManagementView);
mockAlertsViewComponent = TypeMoq.Mock.ofType<TestJobManagementView>(TestJobManagementView);
mockOperatorsViewComponent = TypeMoq.Mock.ofType<TestJobManagementView>(TestJobManagementView);
mockProxiesViewComponent = TypeMoq.Mock.ofType<TestJobManagementView>(TestJobManagementView);
mockJobManagementService = TypeMoq.Mock.ofType<JobManagementService>(JobManagementService);
let resultStatus: azdata.ResultStatus = {
success: true,
errorMessage: null
};
mockJobManagementService.setup(s => s.jobAction(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(resultStatus));
mockJobManagementService.setup(s => s.deleteJobStep(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(resultStatus));
mockJobManagementService.setup(s => s.deleteProxy(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(resultStatus));
mockJobManagementService.setup(s => s.deleteOperator(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(resultStatus));
mockJobManagementService.setup(s => s.deleteAlert(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(resultStatus));
});
test('Jobs Refresh Action', async () => {
mockRefreshAction = TypeMoq.Mock.ofType(JobsRefreshAction, TypeMoq.MockBehavior.Strict, JobsRefreshAction.ID, JobsRefreshAction.LABEL);
mockRefreshAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => mockJobsViewComponent.object.refreshJobs());
mockRefreshAction.setup(s => s.id).returns(() => JobsRefreshAction.ID);
mockRefreshAction.setup(s => s.label).returns(() => JobsRefreshAction.LABEL);
assert.equal(mockRefreshAction.object.id, JobsRefreshAction.ID);
assert.equal(mockRefreshAction.object.label, JobsRefreshAction.LABEL);
// Job Refresh Action from Jobs View should refresh the component
await mockRefreshAction.object.run(null);
mockJobsViewComponent.verify(c => c.refreshJobs(), TypeMoq.Times.once());
});
test('New Job Action', async () => {
mockNewJobAction = TypeMoq.Mock.ofType(NewJobAction, TypeMoq.MockBehavior.Strict, NewJobAction.ID, NewJobAction.LABEL);
mockNewJobAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => mockJobsViewComponent.object.openCreateJobDialog());
mockNewJobAction.setup(s => s.id).returns(() => NewJobAction.ID);
mockNewJobAction.setup(s => s.label).returns(() => NewJobAction.LABEL);
assert.equal(mockNewJobAction.object.id, NewJobAction.ID);
assert.equal(mockNewJobAction.object.label, NewJobAction.LABEL);
// New Job Action from Jobs View should open a dialog
await mockNewJobAction.object.run(null);
mockJobsViewComponent.verify(c => c.openCreateJobDialog(), TypeMoq.Times.once());
});
test('Edit Job Action', async () => {
mockEditJobAction = TypeMoq.Mock.ofType(EditJobAction, TypeMoq.MockBehavior.Strict, EditJobAction.ID, EditJobAction.LABEL);
let commandServiceCalled: boolean = false;
mockEditJobAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => {
commandServiceCalled = true;
return Promise.resolve(commandServiceCalled);
});
mockEditJobAction.setup(s => s.id).returns(() => EditJobAction.ID);
mockEditJobAction.setup(s => s.label).returns(() => EditJobAction.LABEL);
assert.equal(mockEditJobAction.object.id, EditJobAction.ID);
assert.equal(mockEditJobAction.object.label, EditJobAction.LABEL);
// Edit Job Action from Jobs View should open a dialog
await mockEditJobAction.object.run(null);
assert(commandServiceCalled);
});
test('Run Job Action', async () => {
mockRunJobAction = TypeMoq.Mock.ofType(RunJobAction, TypeMoq.MockBehavior.Strict, RunJobAction.ID, RunJobAction.LABEL, null, null, mockJobManagementService);
mockRunJobAction.setup(s => s.run(TypeMoq.It.isAny())).returns(async () => {
let result = await mockJobManagementService.object.jobAction(null, null, null).then((result) => result.success);
return result;
});
mockRunJobAction.setup(s => s.id).returns(() => RunJobAction.ID);
mockRunJobAction.setup(s => s.label).returns(() => RunJobAction.LABEL);
assert.equal(mockRunJobAction.object.id, RunJobAction.ID);
assert.equal(mockRunJobAction.object.label, RunJobAction.LABEL);
// Run Job Action should make the Job Management service call job action
await mockRunJobAction.object.run(null);
mockJobManagementService.verify(s => s.jobAction(null, null, null), TypeMoq.Times.once());
});
test('Stop Job Action', async () => {
mockStopJobAction = TypeMoq.Mock.ofType(StopJobAction, TypeMoq.MockBehavior.Strict, StopJobAction.ID, StopJobAction.LABEL, null, null, mockJobManagementService);
mockStopJobAction.setup(s => s.run(TypeMoq.It.isAny())).returns(async () => {
let result = await mockJobManagementService.object.jobAction(null, null, null).then((result) => result.success);
return result;
});
mockStopJobAction.setup(s => s.id).returns(() => RunJobAction.ID);
mockStopJobAction.setup(s => s.label).returns(() => RunJobAction.LABEL);
assert.equal(mockStopJobAction.object.id, RunJobAction.ID);
assert.equal(mockStopJobAction.object.label, RunJobAction.LABEL);
// Run Job Action should make the Job Management service call job action
await mockStopJobAction.object.run(null);
mockJobManagementService.verify(s => s.jobAction(null, null, null), TypeMoq.Times.once());
});
test('Delete Job Action', async () => {
mockDeleteJobAction = TypeMoq.Mock.ofType(DeleteJobAction, TypeMoq.MockBehavior.Strict, DeleteJobAction.ID, DeleteJobAction.LABEL, null, null, mockJobManagementService);
mockDeleteJobAction.setup(s => s.run(TypeMoq.It.isAny())).returns(async () => {
let result = await mockJobManagementService.object.jobAction(null, null, null).then((result) => result.success);
return result;
});
mockDeleteJobAction.setup(s => s.id).returns(() => DeleteJobAction.ID);
mockDeleteJobAction.setup(s => s.label).returns(() => DeleteJobAction.LABEL);
assert.equal(mockDeleteJobAction.object.id, DeleteJobAction.ID);
assert.equal(mockDeleteJobAction.object.label, DeleteJobAction.LABEL);
// Run Job Action should make the Job Management service call job action
await mockDeleteJobAction.object.run(null);
mockJobManagementService.verify(s => s.jobAction(null, null, null), TypeMoq.Times.once());
});
// Step Actions
test('New Step Action', async () => {
mockNewStepAction = TypeMoq.Mock.ofType(NewStepAction, TypeMoq.MockBehavior.Strict, NewJobAction.ID, NewJobAction.LABEL);
let commandServiceCalled = false;
mockNewStepAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => {
commandServiceCalled = true;
return Promise.resolve(commandServiceCalled);
});
mockNewStepAction.setup(s => s.id).returns(() => NewJobAction.ID);
mockNewStepAction.setup(s => s.label).returns(() => NewJobAction.LABEL);
assert.equal(mockNewStepAction.object.id, NewJobAction.ID);
assert.equal(mockNewStepAction.object.label, NewJobAction.LABEL);
// New Step Action should called command service
await mockNewStepAction.object.run(null);
assert(commandServiceCalled);
});
test('Delete Step Action', async () => {
mockDeleteStepAction = TypeMoq.Mock.ofType(DeleteStepAction, TypeMoq.MockBehavior.Strict, DeleteStepAction.ID, DeleteStepAction.LABEL);
let commandServiceCalled = false;
mockDeleteStepAction.setup(s => s.run(TypeMoq.It.isAny())).returns(async () => {
commandServiceCalled = true;
await mockJobManagementService.object.deleteJobStep(null, null).then((result) => result.success);
return commandServiceCalled;
});
mockDeleteStepAction.setup(s => s.id).returns(() => DeleteStepAction.ID);
mockDeleteStepAction.setup(s => s.label).returns(() => DeleteStepAction.LABEL);
assert.equal(mockDeleteStepAction.object.id, DeleteStepAction.ID);
assert.equal(mockDeleteStepAction.object.label, DeleteStepAction.LABEL);
// Delete Step Action should called command service
await mockDeleteStepAction.object.run(null);
assert(commandServiceCalled);
mockJobManagementService.verify(s => s.deleteJobStep(null, null), TypeMoq.Times.once());
});
// Alert Actions
test('New Alert Action', async () => {
mockNewAlertAction = TypeMoq.Mock.ofType(NewJobAction, TypeMoq.MockBehavior.Strict, NewJobAction.ID, NewJobAction.LABEL);
mockNewAlertAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => mockAlertsViewComponent.object.openCreateAlertDialog());
mockNewAlertAction.setup(s => s.id).returns(() => NewJobAction.ID);
mockNewAlertAction.setup(s => s.label).returns(() => NewJobAction.LABEL);
assert.equal(mockNewAlertAction.object.id, NewJobAction.ID);
assert.equal(mockNewAlertAction.object.label, NewJobAction.LABEL);
// New Alert Action from Alerts View should open a dialog
await mockNewAlertAction.object.run(null);
mockAlertsViewComponent.verify(c => c.openCreateAlertDialog(), TypeMoq.Times.once());
});
test('Edit Alert Action', async () => {
mockEditAlertAction = TypeMoq.Mock.ofType(EditAlertAction, TypeMoq.MockBehavior.Strict, EditAlertAction.ID, EditAlertAction.LABEL);
let commandServiceCalled: boolean = false;
mockEditAlertAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => {
commandServiceCalled = true;
return Promise.resolve(commandServiceCalled);
});
mockEditAlertAction.setup(s => s.id).returns(() => EditAlertAction.ID);
mockEditAlertAction.setup(s => s.label).returns(() => EditAlertAction.LABEL);
assert.equal(mockEditAlertAction.object.id, EditAlertAction.ID);
assert.equal(mockEditAlertAction.object.label, EditAlertAction.LABEL);
// Edit Alert Action from Jobs View should open a dialog
await mockEditAlertAction.object.run(null);
assert(commandServiceCalled);
});
test('Delete Alert Action', async () => {
mockDeleteAlertAction = TypeMoq.Mock.ofType(DeleteAlertAction, TypeMoq.MockBehavior.Strict, DeleteAlertAction.ID, DeleteAlertAction.LABEL, null, null, mockJobManagementService);
let commandServiceCalled = false;
mockDeleteAlertAction.setup(s => s.run(TypeMoq.It.isAny())).returns(async () => {
commandServiceCalled = true;
await mockJobManagementService.object.deleteAlert(null, null).then((result) => result.success);
return commandServiceCalled;
});
mockDeleteAlertAction.setup(s => s.id).returns(() => DeleteAlertAction.ID);
mockDeleteAlertAction.setup(s => s.label).returns(() => DeleteAlertAction.LABEL);
assert.equal(mockDeleteAlertAction.object.id, DeleteAlertAction.ID);
assert.equal(mockDeleteAlertAction.object.label, DeleteAlertAction.LABEL);
// Delete Alert Action should call job management service
await mockDeleteAlertAction.object.run(null);
assert(commandServiceCalled);
mockJobManagementService.verify(s => s.deleteAlert(null, null), TypeMoq.Times.once());
});
// Operator Tests
test('New Operator Action', async () => {
mockNewOperatorAction = TypeMoq.Mock.ofType(NewOperatorAction, TypeMoq.MockBehavior.Strict, NewOperatorAction.ID, NewOperatorAction.LABEL);
mockNewOperatorAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => mockOperatorsViewComponent.object.openCreateOperatorDialog());
mockNewOperatorAction.setup(s => s.id).returns(() => NewOperatorAction.ID);
mockNewOperatorAction.setup(s => s.label).returns(() => NewOperatorAction.LABEL);
assert.equal(mockNewOperatorAction.object.id, NewOperatorAction.ID);
assert.equal(mockNewOperatorAction.object.label, NewOperatorAction.LABEL);
// New Operator Action from Operators View should open a dialog
await mockNewOperatorAction.object.run(null);
mockOperatorsViewComponent.verify(c => c.openCreateOperatorDialog(), TypeMoq.Times.once());
});
test('Edit Operator Action', async () => {
mockEditOperatorAction = TypeMoq.Mock.ofType(EditOperatorAction, TypeMoq.MockBehavior.Strict, EditOperatorAction.ID, EditOperatorAction.LABEL);
let commandServiceCalled: boolean = false;
mockEditOperatorAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => {
commandServiceCalled = true;
return Promise.resolve(commandServiceCalled);
});
mockEditOperatorAction.setup(s => s.id).returns(() => EditOperatorAction.ID);
mockEditOperatorAction.setup(s => s.label).returns(() => EditOperatorAction.LABEL);
assert.equal(mockEditOperatorAction.object.id, EditOperatorAction.ID);
assert.equal(mockEditOperatorAction.object.label, EditOperatorAction.LABEL);
// Edit Operator Action from Jobs View should open a dialog
await mockEditOperatorAction.object.run(null);
assert(commandServiceCalled);
});
test('Delete Operator Action', async () => {
mockDeleteOperatorAction = TypeMoq.Mock.ofType(DeleteOperatorAction, TypeMoq.MockBehavior.Strict, DeleteOperatorAction.ID, DeleteOperatorAction.LABEL, null, null, mockJobManagementService);
let commandServiceCalled = false;
mockDeleteOperatorAction.setup(s => s.run(TypeMoq.It.isAny())).returns(async () => {
commandServiceCalled = true;
await mockJobManagementService.object.deleteOperator(null, null).then((result) => result.success);
return commandServiceCalled;
});
mockDeleteOperatorAction.setup(s => s.id).returns(() => DeleteOperatorAction.ID);
mockDeleteOperatorAction.setup(s => s.label).returns(() => DeleteOperatorAction.LABEL);
assert.equal(mockDeleteOperatorAction.object.id, DeleteOperatorAction.ID);
assert.equal(mockDeleteOperatorAction.object.label, DeleteOperatorAction.LABEL);
// Delete Operator Action should call job management service
await mockDeleteOperatorAction.object.run(null);
assert(commandServiceCalled);
mockJobManagementService.verify(s => s.deleteOperator(null, null), TypeMoq.Times.once());
});
// Proxy Actions
test('New Proxy Action', async () => {
mockNewProxyAction = TypeMoq.Mock.ofType(NewProxyAction, TypeMoq.MockBehavior.Strict, NewProxyAction.ID, NewProxyAction.LABEL);
mockNewProxyAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => mockProxiesViewComponent.object.openCreateProxyDialog());
mockNewProxyAction.setup(s => s.id).returns(() => NewProxyAction.ID);
mockNewProxyAction.setup(s => s.label).returns(() => NewProxyAction.LABEL);
assert.equal(mockNewProxyAction.object.id, NewProxyAction.ID);
assert.equal(mockNewProxyAction.object.label, NewProxyAction.LABEL);
// New Proxy Action from Alerts View should open a dialog
await mockNewProxyAction.object.run(null);
mockProxiesViewComponent.verify(c => c.openCreateProxyDialog(), TypeMoq.Times.once());
});
test('Edit Proxy Action', async () => {
mockEditProxyAction = TypeMoq.Mock.ofType(EditProxyAction, TypeMoq.MockBehavior.Strict, EditProxyAction.ID, EditProxyAction.LABEL);
let commandServiceCalled: boolean = false;
mockEditProxyAction.setup(s => s.run(TypeMoq.It.isAny())).returns(() => {
commandServiceCalled = true;
return Promise.resolve(commandServiceCalled);
});
mockEditProxyAction.setup(s => s.id).returns(() => EditProxyAction.ID);
mockEditProxyAction.setup(s => s.label).returns(() => EditProxyAction.LABEL);
assert.equal(mockEditProxyAction.object.id, EditProxyAction.ID);
assert.equal(mockEditProxyAction.object.label, EditProxyAction.LABEL);
// Edit Proxy Action from Proxies View should open a dialog
await mockEditProxyAction.object.run(null);
assert(commandServiceCalled);
});
test('Delete Proxy Action', async () => {
mockDeleteProxyAction = TypeMoq.Mock.ofType(DeleteProxyAction, TypeMoq.MockBehavior.Strict, DeleteProxyAction.ID, DeleteProxyAction.LABEL, null, null, mockJobManagementService);
let commandServiceCalled = false;
mockDeleteProxyAction.setup(s => s.run(TypeMoq.It.isAny())).returns(async () => {
commandServiceCalled = true;
await mockJobManagementService.object.deleteProxy(null, null).then((result) => result.success);
return commandServiceCalled;
});
mockDeleteProxyAction.setup(s => s.id).returns(() => DeleteProxyAction.ID);
mockDeleteProxyAction.setup(s => s.label).returns(() => DeleteProxyAction.LABEL);
assert.equal(mockDeleteProxyAction.object.id, DeleteProxyAction.ID);
assert.equal(mockDeleteProxyAction.object.label, DeleteProxyAction.LABEL);
// Delete Proxy Action should call job management service
await mockDeleteProxyAction.object.run(null);
assert(commandServiceCalled);
mockJobManagementService.verify(s => s.deleteProxy(null, null), TypeMoq.Times.once());
});
});

View File

@@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IComponentEventArgs } from 'sql/workbench/browser/modelComponents/interfaces';
import { Event } from 'vs/base/common/event';
import { IComponentEventArgs, ModelComponentTypes } from 'sql/platform/dashboard/browser/interfaces';
export interface IView {
readonly id: string;
@@ -14,6 +13,19 @@ export interface IView {
readonly serverInfo: azdata.ServerInfo;
}
export interface IComponentShape {
type: ModelComponentTypes;
id: string;
properties?: { [key: string]: any };
layout?: any;
itemConfigs?: IItemConfig[];
}
export interface IItemConfig {
componentShape: IComponentShape;
config: any;
}
export interface IModelViewEventArgs extends IComponentEventArgs {
isRootComponent: boolean;
}

View File

@@ -1,141 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as types from 'vs/base/common/types';
import { SaveFormat } from 'sql/workbench/contrib/grid/common/interfaces';
export interface IGridDataProvider {
/**
* Gets N rows of data
* @param rowStart 0-indexed start row to retrieve data from
* @param numberOfRows total number of rows of data to retrieve
*/
getRowData(rowStart: number, numberOfRows: number): Thenable<azdata.QueryExecuteSubsetResult>;
/**
* Sends a copy request to copy data to the clipboard
* @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[], includeHeaders?: boolean): Promise<void>;
/**
* Gets the EOL terminator to use for this data type.
*/
getEolString(): string;
shouldIncludeHeaders(includeHeaders: boolean): boolean;
shouldRemoveNewLines(): boolean;
getColumnHeaders(range: Slick.Range): string[] | undefined;
readonly canSerialize: boolean;
serializeResults(format: SaveFormat, selection: Slick.Range[]): Thenable<void>;
}
export async function getResultsString(provider: IGridDataProvider, selection: Slick.Range[], includeHeaders?: boolean): Promise<string> {
let headers: Map<number, string> = new Map(); // Maps a column index -> header
let rows: Map<number, Map<number, string>> = new Map(); // Maps row index -> column index -> actual row value
const eol = provider.getEolString();
// create a mapping of the ranges to get promises
let tasks: (() => Promise<void>)[] = selection.map((range) => {
return async (): Promise<void> => {
let startCol = range.fromCell;
let startRow = range.fromRow;
const result = await provider.getRowData(range.fromRow, range.toRow - range.fromRow + 1);
// If there was a previous selection separate it with a line break. Currently
// when there are multiple selections they are never on the same line
let columnHeaders = provider.getColumnHeaders(range);
if (columnHeaders !== undefined) {
let idx = 0;
for (let header of columnHeaders) {
headers.set(startCol + idx, header);
idx++;
}
}
// Iterate over the rows to paste into the copy string
for (let rowIndex: number = 0; rowIndex < result.resultSubset.rows.length; rowIndex++) {
let row = result.resultSubset.rows[rowIndex];
let cellObjects = row.slice(range.fromCell, (range.toCell + 1));
// Remove newlines if requested
let cells = provider.shouldRemoveNewLines()
? cellObjects.map(x => removeNewLines(x.displayValue))
: cellObjects.map(x => x.displayValue);
let idx = 0;
for (let cell of cells) {
let map = rows.get(rowIndex + startRow);
if (!map) {
map = new Map();
rows.set(rowIndex + startRow, map);
}
map.set(startCol + idx, cell);
idx++;
}
}
};
});
// Set the tasks gathered above to execute
let actionedTasks: Promise<void>[] = tasks.map(t => { return t(); });
// Make sure all these tasks have executed
await Promise.all(actionedTasks);
const sortResults = (e1: [number, any], e2: [number, any]) => {
return e1[0] - e2[0];
};
headers = new Map([...headers].sort(sortResults));
rows = new Map([...rows].sort(sortResults));
let copyString = '';
if (includeHeaders) {
copyString = [...headers.values()].join('\t').concat(eol);
}
const rowKeys = [...headers.keys()];
for (let rowEntry of rows) {
let rowMap = rowEntry[1];
for (let rowIdx of rowKeys) {
let value = rowMap.get(rowIdx);
if (value) {
copyString = copyString.concat(value);
}
copyString = copyString.concat('\t');
}
// Removes the tab seperator from the end of a row
copyString = copyString.slice(0, -1 * '\t'.length);
copyString = copyString.concat(eol);
}
// Removes EoL from the end of the result
copyString = copyString.slice(0, -1 * eol.length);
return copyString;
}
function removeNewLines(inputString: string): string {
// This regex removes all newlines in all OS types
// Windows(CRLF): \r\n
// Linux(LF)/Modern MacOS: \n
// Old MacOs: \r
if (types.isUndefinedOrNull(inputString)) {
return 'null';
}
let outputString: string = inputString.replace(/(\r\n|\n|\r)/gm, '');
return outputString;
}

View File

@@ -1,352 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import QueryRunner from 'sql/platform/query/common/queryRunner';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as azdata from 'azdata';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { Event, Emitter } from 'vs/base/common/event';
import { keys } from 'vs/base/common/map';
import { assign } from 'vs/base/common/objects';
import { IAdsTelemetryService, ITelemetryEventProperties } from 'sql/platform/telemetry/common/telemetry';
export const SERVICE_ID = 'queryManagementService';
export const IQueryManagementService = createDecorator<IQueryManagementService>(SERVICE_ID);
export interface IQueryManagementService {
_serviceBrand: undefined;
onHandlerAdded: Event<string>;
addQueryRequestHandler(queryType: string, runner: IQueryRequestHandler): IDisposable;
isProviderRegistered(providerId: string): boolean;
getRegisteredProviders(): string[];
registerRunner(runner: QueryRunner, uri: string): void;
cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult>;
runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
runQueryStatement(ownerUri: string, line: number, column: number): Promise<void>;
runQueryString(ownerUri: string, queryString: string): Promise<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult>;
parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult>;
getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<azdata.QueryExecuteSubsetResult>;
disposeQuery(ownerUri: string): Promise<void>;
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult>;
setQueryExecutionOptions(uri: string, options: azdata.QueryExecutionOptions): Promise<void>;
// Callbacks
onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void;
onBatchStart(batchInfo: azdata.QueryExecuteBatchNotificationParams): void;
onBatchComplete(batchInfo: azdata.QueryExecuteBatchNotificationParams): void;
onResultSetAvailable(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void;
onResultSetUpdated(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void;
onMessage(message: azdata.QueryExecuteMessageParams): void;
// Edit Data Callbacks
onEditSessionReady(ownerUri: string, success: boolean, message: string): void;
// Edit Data Functions
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void>;
disposeEdit(ownerUri: string): Promise<void>;
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult>;
commitEdit(ownerUri: string): Promise<void>;
createRow(ownerUri: string): Promise<azdata.EditCreateRowResult>;
deleteRow(ownerUri: string, rowId: number): Promise<void>;
revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult>;
revertRow(ownerUri: string, rowId: number): Promise<void>;
getEditRows(rowData: azdata.EditSubsetParams): Promise<azdata.EditSubsetResult>;
}
/*
* An object that can handle basic request-response actions related to queries
*/
export interface IQueryRequestHandler {
cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult>;
runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
runQueryStatement(ownerUri: string, line: number, column: number): Promise<void>;
runQueryString(ownerUri: string, queryString: string): Promise<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult>;
parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult>;
getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<azdata.QueryExecuteSubsetResult>;
disposeQuery(ownerUri: string): Promise<void>;
saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult>;
setQueryExecutionOptions(ownerUri: string, options: azdata.QueryExecutionOptions): Promise<void>;
// Edit Data actions
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void>;
disposeEdit(ownerUri: string): Promise<void>;
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult>;
commitEdit(ownerUri: string): Promise<void>;
createRow(ownerUri: string): Promise<azdata.EditCreateRowResult>;
deleteRow(ownerUri: string, rowId: number): Promise<void>;
revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult>;
revertRow(ownerUri: string, rowId: number): Promise<void>;
getEditRows(rowData: azdata.EditSubsetParams): Promise<azdata.EditSubsetResult>;
}
export class QueryManagementService implements IQueryManagementService {
public _serviceBrand: undefined;
private _requestHandlers = new Map<string, IQueryRequestHandler>();
private _onHandlerAddedEmitter = new Emitter<string>();
// public for testing only
public _queryRunners = new Map<string, QueryRunner>();
// public for testing only
public _handlerCallbackQueue: ((run: QueryRunner) => void)[] = [];
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
) {
}
// Registers queryRunners with their uris to distribute notifications.
// Ensures that notifications are handled in the correct order by handling
// enqueued handlers first.
// public for testing only
public registerRunner(runner: QueryRunner, uri: string): void {
// If enqueueOrRun was called before registerRunner for the current query,
// _handlerCallbackQueue will be non-empty. Run all handlers in the queue first
// so that notifications are handled in order they arrived
while (this._handlerCallbackQueue.length > 0) {
let handler = this._handlerCallbackQueue.shift()!;
handler(runner);
}
// Set the runner for any other handlers if the runner is in use by the
// current query or a subsequent query
if (!runner.hasCompleted) {
this._queryRunners.set(uri, runner);
}
}
// Handles logic to run the given handlerCallback at the appropriate time. If the given runner is
// undefined, the handlerCallback is put on the _handlerCallbackQueue to be run once the runner is set
// public for testing only
private enqueueOrRun(handlerCallback: (runnerParam: QueryRunner) => void, runner: QueryRunner): void {
if (runner === undefined) {
this._handlerCallbackQueue.push(handlerCallback);
} else {
handlerCallback(runner);
}
}
private _notify(ownerUri: string, sendNotification: (runner: QueryRunner) => void): void {
let runner = this._queryRunners.get(ownerUri);
this.enqueueOrRun(sendNotification, runner!);
}
public addQueryRequestHandler(queryType: string, handler: IQueryRequestHandler): IDisposable {
this._requestHandlers.set(queryType, handler);
this._onHandlerAddedEmitter.fire(queryType);
return {
dispose: () => {
}
};
}
public get onHandlerAdded(): Event<string> {
return this._onHandlerAddedEmitter.event;
}
public isProviderRegistered(providerId: string): boolean {
let handler = this._requestHandlers.get(providerId);
return !!handler;
}
public getRegisteredProviders(): string[] {
return Array.from(keys(this._requestHandlers));
}
private addTelemetry(eventName: string, ownerUri: string, runOptions?: azdata.ExecutionPlanOptions): void {
const providerId: string = this._connectionService.getProviderIdFromUri(ownerUri);
const data: ITelemetryEventProperties = {
provider: providerId,
};
if (runOptions) {
assign(data, {
displayEstimatedQueryPlan: runOptions.displayEstimatedQueryPlan,
displayActualQueryPlan: runOptions.displayActualQueryPlan
});
}
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, eventName).withAdditionalProperties(data).send();
}
private _runAction<T>(uri: string, action: (handler: IQueryRequestHandler) => Promise<T>, fallBackToDefaultProvider: boolean = false): Promise<T> {
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
if (!providerId && fallBackToDefaultProvider) {
providerId = this._connectionService.getDefaultProviderId();
}
if (!providerId) {
return Promise.reject(new Error('Connection is required in order to interact with queries'));
}
let handler = this._requestHandlers.get(providerId);
if (handler) {
return action(handler);
} else {
return Promise.reject(new Error('No Handler Registered'));
}
}
public cancelQuery(ownerUri: string): Promise<azdata.QueryCancelResult> {
this.addTelemetry(TelemetryKeys.CancelQuery, ownerUri);
return this._runAction(ownerUri, (runner) => {
return runner.cancelQuery(ownerUri);
});
}
public runQuery(ownerUri: string, selection: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
this.addTelemetry(TelemetryKeys.RunQuery, ownerUri, runOptions);
return this._runAction(ownerUri, (runner) => {
return runner.runQuery(ownerUri, selection, runOptions);
});
}
public runQueryStatement(ownerUri: string, line: number, column: number): Promise<void> {
this.addTelemetry(TelemetryKeys.RunQueryStatement, ownerUri);
return this._runAction(ownerUri, (runner) => {
return runner.runQueryStatement(ownerUri, line, column);
});
}
public runQueryString(ownerUri: string, queryString: string): Promise<void> {
this.addTelemetry(TelemetryKeys.RunQueryString, ownerUri);
return this._runAction(ownerUri, (runner) => {
return runner.runQueryString(ownerUri, queryString);
});
}
public runQueryAndReturn(ownerUri: string, queryString: string): Promise<azdata.SimpleExecuteResult> {
return this._runAction(ownerUri, (runner) => {
return runner.runQueryAndReturn(ownerUri, queryString);
});
}
public parseSyntax(ownerUri: string, query: string): Promise<azdata.SyntaxParseResult> {
return this._runAction(ownerUri, (runner) => {
return runner.parseSyntax(ownerUri, query);
});
}
public getQueryRows(rowData: azdata.QueryExecuteSubsetParams): Promise<azdata.QueryExecuteSubsetResult> {
return this._runAction(rowData.ownerUri, (runner) => {
return runner.getQueryRows(rowData);
});
}
public disposeQuery(ownerUri: string): Promise<void> {
this._queryRunners.delete(ownerUri);
return this._runAction(ownerUri, (runner) => {
return runner.disposeQuery(ownerUri);
});
}
public setQueryExecutionOptions(ownerUri: string, options: azdata.QueryExecutionOptions): Promise<void> {
return this._runAction(ownerUri, (runner) => {
return runner.setQueryExecutionOptions(ownerUri, options);
}, true);
}
public saveResults(requestParams: azdata.SaveResultsRequestParams): Promise<azdata.SaveResultRequestResult> {
return this._runAction(requestParams.ownerUri, (runner) => {
return runner.saveResults(requestParams);
});
}
public onQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void {
this._notify(result.ownerUri, (runner: QueryRunner) => {
runner.handleQueryComplete(result);
});
}
public onBatchStart(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
this._notify(batchInfo.ownerUri, (runner: QueryRunner) => {
runner.handleBatchStart(batchInfo);
});
}
public onBatchComplete(batchInfo: azdata.QueryExecuteBatchNotificationParams): void {
this._notify(batchInfo.ownerUri, (runner: QueryRunner) => {
runner.handleBatchComplete(batchInfo);
});
}
public onResultSetAvailable(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
runner.handleResultSetAvailable(resultSetInfo);
});
}
public onResultSetUpdated(resultSetInfo: azdata.QueryExecuteResultSetNotificationParams): void {
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
runner.handleResultSetUpdated(resultSetInfo);
});
}
public onMessage(message: azdata.QueryExecuteMessageParams): void {
this._notify(message.ownerUri, (runner: QueryRunner) => {
runner.handleMessage(message);
});
}
// Edit Data Functions
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
return this._runAction(ownerUri, (runner) => {
return runner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
});
}
public onEditSessionReady(ownerUri: string, success: boolean, message: string): void {
this._notify(ownerUri, (runner: QueryRunner) => {
runner.handleEditSessionReady(ownerUri, success, message);
});
}
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult> {
return this._runAction(ownerUri, (runner) => {
return runner.updateCell(ownerUri, rowId, columnId, newValue);
});
}
public commitEdit(ownerUri: string): Promise<void> {
return this._runAction(ownerUri, (runner) => {
return runner.commitEdit(ownerUri);
});
}
public createRow(ownerUri: string): Promise<azdata.EditCreateRowResult> {
return this._runAction(ownerUri, (runner) => {
return runner.createRow(ownerUri);
});
}
public deleteRow(ownerUri: string, rowId: number): Promise<void> {
return this._runAction(ownerUri, (runner) => {
return runner.deleteRow(ownerUri, rowId);
});
}
public disposeEdit(ownerUri: string): Promise<void> {
return this._runAction(ownerUri, (runner) => {
return runner.disposeEdit(ownerUri);
});
}
public revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult> {
return this._runAction(ownerUri, (runner) => {
return runner.revertCell(ownerUri, rowId, columnId);
});
}
public revertRow(ownerUri: string, rowId: number): Promise<void> {
return this._runAction(ownerUri, (runner) => {
return runner.revertRow(ownerUri, rowId);
});
}
public getEditRows(rowData: azdata.EditSubsetParams): Promise<azdata.EditSubsetResult> {
return this._runAction(rowData.ownerUri, (runner) => {
return runner.getEditRows(rowData);
});
}
}

View File

@@ -1,90 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import QueryRunner, { IQueryMessage } from 'sql/platform/query/common/queryRunner';
import { DataService } from 'sql/workbench/contrib/grid/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,
EditUpdateCellResult,
EditSessionReadyParams,
EditSubsetResult,
EditCreateRowResult,
EditRevertCellResult,
ExecutionPlanOptions,
queryeditor
} from 'azdata';
import { QueryInfo } from 'sql/platform/query/common/queryModelService';
export const SERVICE_ID = 'queryModelService';
export const IQueryModelService = createDecorator<IQueryModelService>(SERVICE_ID);
export interface IQueryPlanInfo {
providerId: string;
fileUri: string;
planXml: string;
}
export interface IQueryInfo {
selection: ISelectionData[];
messages: IQueryMessage[];
}
export interface IQueryEvent {
type: queryeditor.QueryEventType;
uri: string;
queryInfo: IQueryInfo;
params?: any;
}
/**
* Interface for the logic of handling running queries and grid interactions for all URIs.
*/
export interface IQueryModelService {
_serviceBrand: undefined;
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;
cancelQuery(input: QueryRunner | string): void;
disposeQuery(uri: string): void;
isRunningQuery(uri: string): boolean;
getDataService(uri: string): DataService;
refreshResultsets(uri: string): void;
sendGridContentEvent(uri: string, eventName: string): void;
resizeResultsets(uri: string): void;
onLoaded(uri: string): void;
copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void;
showCommitError(error: string): void;
onRunQueryStart: Event<string>;
onRunQueryUpdate: Event<string>;
onRunQueryComplete: Event<string>;
onQueryEvent: Event<IQueryEvent>;
// Edit Data Functions
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void;
disposeEdit(ownerUri: string): Promise<void>;
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<EditUpdateCellResult | undefined>;
commitEdit(ownerUri: string): Promise<void>;
createRow(ownerUri: string): Promise<EditCreateRowResult | undefined>;
deleteRow(ownerUri: string, rowId: number): Promise<void>;
revertCell(ownerUri: string, rowId: number, columnId: number): Promise<EditRevertCellResult | undefined>;
revertRow(ownerUri: string, rowId: number): Promise<void>;
getEditRows(ownerUri: string, rowStart: number, numberOfRows: number): Promise<EditSubsetResult | undefined>;
_getQueryInfo(uri: string): QueryInfo | undefined;
// Edit Data Callbacks
onEditSessionReady: Event<EditSessionReadyParams>;
}

View File

@@ -1,669 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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 QueryRunner from 'sql/platform/query/common/queryRunner';
import { DataService } from 'sql/workbench/contrib/grid/common/dataService';
import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel';
import * as azdata from 'azdata';
import * as nls from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
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;
export interface QueryEvent {
type: string;
data: any;
}
/**
* Holds information about the state of a query runner
*/
export class QueryInfo {
public queryRunner: QueryRunner;
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
// via the data service will be lost.
public dataServiceReady: boolean;
constructor() {
this.dataServiceReady = false;
this.queryEventQueue = [];
this.selection = [];
}
}
/**
* Handles running queries and grid interactions for all URIs. Interacts with each URI's results grid via a DataService instance
*/
export class QueryModelService implements IQueryModelService {
_serviceBrand: undefined;
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _queryInfoMap: Map<string, QueryInfo>;
private _onRunQueryStart: Emitter<string>;
private _onRunQueryUpdate: Emitter<string>;
private _onRunQueryComplete: Emitter<string>;
private _onQueryEvent: Emitter<IQueryEvent>;
private _onEditSessionReady: Emitter<azdata.EditSessionReadyParams>;
// EVENTS /////////////////////////////////////////////////////////////
public get onRunQueryStart(): Event<string> { return this._onRunQueryStart.event; }
public get onRunQueryUpdate(): Event<string> { return this._onRunQueryUpdate.event; }
public get onRunQueryComplete(): Event<string> { return this._onRunQueryComplete.event; }
public get onQueryEvent(): Event<IQueryEvent> { return this._onQueryEvent.event; }
public get onEditSessionReady(): Event<azdata.EditSessionReadyParams> { return this._onEditSessionReady.event; }
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@INotificationService private _notificationService: INotificationService
) {
this._queryInfoMap = new Map<string, QueryInfo>();
this._onRunQueryStart = new Emitter<string>();
this._onRunQueryUpdate = new Emitter<string>();
this._onRunQueryComplete = new Emitter<string>();
this._onQueryEvent = new Emitter<IQueryEvent>();
this._onEditSessionReady = new Emitter<azdata.EditSessionReadyParams>();
}
// IQUERYMODEL /////////////////////////////////////////////////////////
public getDataService(uri: string): DataService {
let dataService: DataService | undefined;
if (this._queryInfoMap.has(uri)) {
dataService = this._getQueryInfo(uri)!.dataService;
}
if (!dataService) {
throw new Error('Could not find data service for uri: ' + uri);
}
return dataService;
}
/**
* Force all grids to re-render. This is needed to re-render the grids when switching
* between different URIs.
*/
public refreshResultsets(uri: string): void {
this._fireGridContentEvent(uri, GridContentEvents.RefreshContents);
}
/**
* Resize the grid UI to fit the current screen size.
*/
public resizeResultsets(uri: string): void {
this._fireGridContentEvent(uri, GridContentEvents.ResizeContents);
}
public sendGridContentEvent(uri: string, eventName: string): void {
this._fireGridContentEvent(uri, eventName);
}
/**
* To be called by a component's DataService when the component has finished loading.
* Sends all previously enqueued query events to the DataService and signals to stop enqueuing
* any further events.
*/
public onLoaded(uri: string) {
if (this._queryInfoMap.has(uri)) {
let info = this._getQueryInfo(uri)!;
info.dataServiceReady = true;
this._sendQueuedEvents(uri);
}
}
/**
* Get more data rows from the current resultSets from the service layer
*/
public getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<azdata.ResultSetSubset | undefined> {
if (this._queryInfoMap.has(uri)) {
return this._getQueryInfo(uri)!.queryRunner.getQueryRows(rowStart, numberOfRows, batchId, resultId).then(results => {
return results.resultSubset;
});
} else {
return Promise.resolve(undefined);
}
}
public getEditRows(uri: string, rowStart: number, numberOfRows: number): Promise<azdata.EditSubsetResult | undefined> {
if (this._queryInfoMap.has(uri)) {
return this._queryInfoMap.get(uri)!.queryRunner.getEditRows(rowStart, numberOfRows).then(results => {
return results;
});
} else {
return Promise.resolve(undefined);
}
}
public async copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): Promise<void> {
if (this._queryInfoMap.has(uri)) {
return this._queryInfoMap.get(uri)!.queryRunner.copyResults(selection, batchId, resultId, includeHeaders);
}
}
public showCommitError(error: string): void {
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('commitEditFailed', "Commit row failed: ") + error
});
}
public isRunningQuery(uri: string): boolean {
return !this._queryInfoMap.has(uri)
? false
: this._getQueryInfo(uri)!.queryRunner.isExecuting;
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* Run Query implementation
*/
private async doRunQuery(uri: string, selection: azdata.ISelectionData | string, queryInput: QueryEditorInput,
runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
// Reuse existing query runner if it exists
let queryRunner: QueryRunner | undefined;
let info: QueryInfo;
if (this._queryInfoMap.has(uri)) {
info = this._getQueryInfo(uri)!;
let existingRunner: QueryRunner = info.queryRunner;
// If the query is already in progress, don't attempt to send it
if (existingRunner.isExecuting) {
return;
}
// If the query is not in progress, we can reuse the query runner
queryRunner = existingRunner;
info.selection = [];
info.selectionSnippet = undefined;
} else {
// We do not have a query runner for this editor, so create a new one
// and map it to the results uri
info = this.initQueryRunner(uri);
queryRunner = info.queryRunner;
}
info.queryInput = queryInput;
if (types.isString(selection)) {
// Run the query string in this case
if (selection.length < selectionSnippetMaxLen) {
info.selectionSnippet = selection;
} else {
info.selectionSnippet = selection.substring(0, selectionSnippetMaxLen - 3) + '...';
}
return queryRunner.runQuery(selection, runOptions);
} else if (runCurrentStatement) {
return queryRunner.runQueryStatement(selection);
} else {
return queryRunner.runQuery(selection, runOptions);
}
}
private initQueryRunner(uri: string): QueryInfo {
let queryRunner = this._instantiationService.createInstance(QueryRunner, uri);
let info = new QueryInfo();
queryRunner.onResultSet(e => {
this._fireQueryEvent(uri, 'resultSet', e);
});
queryRunner.onBatchStart(b => {
let link = undefined;
let messageText = LocalizedConstants.runQueryBatchStartMessage;
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
// executed query text
messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet);
} else {
link = {
text: strings.format(LocalizedConstants.runQueryBatchStartLine, b.selection.startLine + 1)
};
}
}
let message = {
message: messageText,
batchId: b.id,
isError: false,
time: new Date().toLocaleTimeString(),
link: link
};
this._fireQueryEvent(uri, 'message', message);
info.selection.push(this._validateSelection(b.selection));
});
queryRunner.onMessage(m => {
this._fireQueryEvent(uri, 'message', m);
});
queryRunner.onQueryEnd(totalMilliseconds => {
this._onRunQueryComplete.fire(uri);
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStop',
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
}
};
this._onQueryEvent.fire(event);
// fire UI event
this._fireQueryEvent(uri, 'complete', totalMilliseconds);
});
queryRunner.onQueryStart(() => {
this._onRunQueryStart.fire(uri);
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStart',
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
}
};
this._onQueryEvent.fire(event);
this._fireQueryEvent(uri, 'start');
});
queryRunner.onResultSetUpdate(() => {
this._onRunQueryUpdate.fire(uri);
let event: IQueryEvent = {
type: 'queryUpdate',
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
}
};
this._onQueryEvent.fire(event);
this._fireQueryEvent(uri, 'update');
});
queryRunner.onQueryPlanAvailable(planInfo => {
// fire extensibility API event
let event: IQueryEvent = {
type: 'executionPlan',
uri: planInfo.fileUri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
params: planInfo
};
this._onQueryEvent.fire(event);
});
queryRunner.onVisualize(resultSetInfo => {
let event: IQueryEvent = {
type: 'visualize',
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
params: resultSetInfo
};
this._onQueryEvent.fire(event);
});
info.queryRunner = queryRunner;
info.dataService = this._instantiationService.createInstance(DataService, uri);
this._queryInfoMap.set(uri, info);
return info;
}
public cancelQuery(input: QueryRunner | string): void {
let queryRunner: QueryRunner | undefined;
if (typeof input === 'string') {
if (this._queryInfoMap.has(input)) {
queryRunner = this._getQueryInfo(input)!.queryRunner;
}
} else {
queryRunner = input;
}
if (queryRunner === undefined || !queryRunner.isExecuting) {
// TODO: Cannot cancel query as no query is running.
return;
}
// Switch the spinner to canceling, which will be reset when the query execute sends back its completed event
// TODO indicate on the status bar that the query is being canceled
// Cancel the query
queryRunner.cancelQuery().then(success => undefined, error => {
// On error, show error message and notify that the query is complete so that buttons and other status indicators
// can be correct
this._notificationService.notify({
severity: Severity.Error,
message: strings.format(LocalizedConstants.msgCancelQueryFailed, error)
});
this._fireQueryEvent(queryRunner!.uri, 'complete', 0);
});
}
public async disposeQuery(ownerUri: string): Promise<void> {
// Get existing query runner
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
await queryRunner.disposeQuery();
}
// remove our info map
if (this._queryInfoMap.has(ownerUri)) {
this._queryInfoMap.delete(ownerUri);
}
}
// EDIT DATA METHODS /////////////////////////////////////////////////////
async initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
// Reuse existing query runner if it exists
let queryRunner: QueryRunner;
let info: QueryInfo;
if (this._queryInfoMap.has(ownerUri)) {
info = this._getQueryInfo(ownerUri)!;
let existingRunner: QueryRunner = info.queryRunner;
// If the initialization is already in progress
if (existingRunner.isExecuting) {
return;
}
queryRunner = existingRunner;
} else {
info = new QueryInfo();
// We do not have a query runner for this editor, so create a new one
// and map it to the results uri
queryRunner = this._instantiationService.createInstance(QueryRunner, ownerUri);
const resultSetEventType = 'resultSet';
queryRunner.onResultSet(resultSet => {
this._fireQueryEvent(ownerUri, resultSetEventType, resultSet);
});
queryRunner.onResultSetUpdate(resultSetSummary => {
this._fireQueryEvent(ownerUri, resultSetEventType, resultSetSummary);
});
queryRunner.onBatchStart(batch => {
let link = undefined;
let messageText = LocalizedConstants.runQueryBatchStartMessage;
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
// executed query text
messageText = nls.localize('runQueryStringBatchStartMessage', "Started executing query \"{0}\"", info.selectionSnippet);
} else {
link = {
text: strings.format(LocalizedConstants.runQueryBatchStartLine, batch.selection.startLine + 1)
};
}
}
let message = {
message: messageText,
batchId: batch.id,
isError: false,
time: new Date().toLocaleTimeString(),
link: link
};
this._fireQueryEvent(ownerUri, 'message', message);
});
queryRunner.onMessage(message => {
this._fireQueryEvent(ownerUri, 'message', message);
});
queryRunner.onQueryEnd(totalMilliseconds => {
this._onRunQueryComplete.fire(ownerUri);
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStop',
uri: ownerUri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
};
this._onQueryEvent.fire(event);
// fire UI event
this._fireQueryEvent(ownerUri, 'complete', totalMilliseconds);
});
queryRunner.onQueryStart(() => {
this._onRunQueryStart.fire(ownerUri);
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStart',
uri: ownerUri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
};
this._onQueryEvent.fire(event);
// fire UI event
this._fireQueryEvent(ownerUri, 'start');
});
queryRunner.onEditSessionReady(e => {
this._onEditSessionReady.fire(e);
this._fireQueryEvent(e.ownerUri, 'editSessionReady');
});
info.queryRunner = queryRunner;
info.dataService = this._instantiationService.createInstance(DataService, ownerUri);
this._queryInfoMap.set(ownerUri, info);
}
if (queryString) {
if (queryString.length < selectionSnippetMaxLen) {
info.selectionSnippet = queryString;
} else {
info.selectionSnippet = queryString.substring(0, selectionSnippetMaxLen - 3) + '...';
}
}
return queryRunner.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString);
}
public cancelInitializeEdit(input: QueryRunner | string): void {
// TODO: Implement query cancellation service
}
public disposeEdit(ownerUri: string): Promise<void> {
// Get existing query runner
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.disposeEdit(ownerUri);
}
return Promise.resolve();
}
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult | undefined> {
// Get existing query runner
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.updateCell(ownerUri, rowId, columnId, newValue).then((result) => result, error => {
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('updateCellFailed', "Update cell failed: ") + error.message
});
return Promise.reject(error);
});
}
return Promise.resolve(undefined);
}
public commitEdit(ownerUri: string): Promise<void> {
// Get existing query runner
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.commitEdit(ownerUri).then(() => { }, error => {
this._notificationService.notify({
severity: Severity.Error,
message: nls.localize('commitEditFailed', "Commit row failed: ") + error.message
});
return Promise.reject(error);
});
}
return Promise.resolve();
}
public createRow(ownerUri: string): Promise<azdata.EditCreateRowResult | undefined> {
// Get existing query runner
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.createRow(ownerUri);
}
return Promise.resolve(undefined);
}
public deleteRow(ownerUri: string, rowId: number): Promise<void> {
// Get existing query runner
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.deleteRow(ownerUri, rowId);
}
return Promise.resolve();
}
public revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult | undefined> {
// Get existing query runner
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.revertCell(ownerUri, rowId, columnId);
}
return Promise.resolve(undefined);
}
public revertRow(ownerUri: string, rowId: number): Promise<void> {
// Get existing query runner
let queryRunner = this.internalGetQueryRunner(ownerUri);
if (queryRunner) {
return queryRunner.revertRow(ownerUri, rowId);
}
return Promise.resolve();
}
public getQueryRunner(ownerUri: string): QueryRunner | undefined {
let queryRunner: QueryRunner | undefined = undefined;
if (this._queryInfoMap.has(ownerUri)) {
queryRunner = this._getQueryInfo(ownerUri)!.queryRunner;
}
// return undefined if not found or is already executing
return queryRunner;
}
// PRIVATE METHODS //////////////////////////////////////////////////////
private internalGetQueryRunner(ownerUri: string): QueryRunner | undefined {
let queryRunner: QueryRunner | undefined;
if (this._queryInfoMap.has(ownerUri)) {
let existingRunner = this._getQueryInfo(ownerUri)!.queryRunner;
// If the query is not already executing then set it up
if (!existingRunner.isExecuting) {
queryRunner = this._getQueryInfo(ownerUri)!.queryRunner;
}
}
// return undefined if not found or is already executing
return queryRunner;
}
private _fireGridContentEvent(uri: string, type: string): void {
let info = this._getQueryInfo(uri);
if (info && info.dataServiceReady) {
let service: DataService = this.getDataService(uri);
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);
}
}
}
private _fireQueryEvent(uri: string, type: string, data?: any) {
let info = this._getQueryInfo(uri);
if (info && info.dataServiceReady) {
let service: DataService = this.getDataService(uri);
service.queryEventObserver.next({
type: type,
data: data
});
} else if (info) {
let queueItem: QueryEvent = { type: type, data: data };
info.queryEventQueue.push(queueItem);
}
}
private _sendQueuedEvents(uri: string): void {
let info = this._getQueryInfo(uri);
while (info && info.queryEventQueue.length > 0) {
let event = info.queryEventQueue.shift()!;
this._fireQueryEvent(uri, event.type, event.data);
}
}
public _getQueryInfo(uri: string): QueryInfo | undefined {
return this._queryInfoMap.get(uri);
}
// TODO remove this funciton and its usages when #821 in vscode-mssql is fixed and
// the SqlToolsService version is updated in this repo - coquagli 4/19/2017
private _validateSelection(selection: azdata.ISelectionData): azdata.ISelectionData {
if (!selection) {
selection = <azdata.ISelectionData>{};
}
selection.endColumn = selection ? Math.max(0, selection.endColumn) : 0;
selection.endLine = selection ? Math.max(0, selection.endLine) : 0;
selection.startColumn = selection ? Math.max(0, selection.startColumn) : 0;
selection.startLine = selection ? Math.max(0, selection.startLine) : 0;
return selection;
}
}

View File

@@ -1,682 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { IQueryManagementService } from 'sql/platform/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/platform/query/common/queryModel';
import { ResultSerializer } from 'sql/workbench/contrib/query/common/resultSerializer';
import Severity from 'vs/base/common/severity';
import * as nls from 'vs/nls';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import * as types from 'vs/base/common/types';
import { Disposable } from 'vs/base/common/lifecycle';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
import { URI } from 'vs/base/common/uri';
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { IGridDataProvider, getResultsString } from 'sql/platform/query/common/gridDataProvider';
import { getErrorMessage } from 'vs/base/common/errors';
import { ILogService } from 'vs/platform/log/common/log';
import { find } from 'vs/base/common/arrays';
export interface IEditSessionReadyEvent {
ownerUri: string;
success: boolean;
message: string;
}
export interface IQueryMessage extends azdata.IResultMessage {
selection?: azdata.ISelectionData;
}
/*
* 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.
*/
export default class QueryRunner extends Disposable {
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _resultLineOffset: number;
private _resultColumnOffset: number;
private _totalElapsedMilliseconds: number = 0;
private _isExecuting: boolean = false;
private _hasCompleted: boolean = false;
private _batchSets: azdata.BatchSummary[] = [];
private _messages: IQueryMessage[] = [];
private registered = false;
private _isQueryPlan: boolean = false;
public get isQueryPlan(): boolean { return this._isQueryPlan; }
private _planXml = new Deferred<string>();
public get planXml(): Promise<string> { return this._planXml.promise; }
private _onMessage = this._register(new Emitter<IQueryMessage>());
public get onMessage(): Event<IQueryMessage> { return this._onMessage.event; } // this is the only way typemoq can moq this... needs investigation @todo anthonydresser 5/2/2019
private _onResultSet = this._register(new Emitter<azdata.ResultSetSummary>());
public readonly onResultSet = this._onResultSet.event;
private _onResultSetUpdate = this._register(new Emitter<azdata.ResultSetSummary>());
public readonly onResultSetUpdate = this._onResultSetUpdate.event;
private _onQueryStart = this._register(new Emitter<void>());
public readonly onQueryStart: Event<void> = this._onQueryStart.event;
private _onQueryEnd = this._register(new Emitter<string>());
public get onQueryEnd(): Event<string> { return this._onQueryEnd.event; }
private _onBatchStart = this._register(new Emitter<azdata.BatchSummary>());
public readonly onBatchStart: Event<azdata.BatchSummary> = this._onBatchStart.event;
private _onBatchEnd = this._register(new Emitter<azdata.BatchSummary>());
public readonly onBatchEnd: Event<azdata.BatchSummary> = this._onBatchEnd.event;
private _onEditSessionReady = this._register(new Emitter<IEditSessionReadyEvent>());
public readonly onEditSessionReady = this._onEditSessionReady.event;
private _onQueryPlanAvailable = this._register(new Emitter<IQueryPlanInfo>());
public readonly onQueryPlanAvailable = this._onQueryPlanAvailable.event;
private _onVisualize = this._register(new Emitter<azdata.ResultSetSummary>());
public readonly onVisualize = this._onVisualize.event;
private _queryStartTime?: Date;
public get queryStartTime(): Date | undefined {
return this._queryStartTime;
}
private _queryEndTime?: Date;
public get queryEndTime(): Date | undefined {
return this._queryEndTime;
}
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
public uri: string,
@IQueryManagementService private _queryManagementService: IQueryManagementService,
@INotificationService private _notificationService: INotificationService,
@IConfigurationService private _configurationService: IConfigurationService,
@IInstantiationService private instantiationService: IInstantiationService,
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService,
@ILogService private _logService: ILogService
) {
super();
}
get isExecuting(): boolean {
return this._isExecuting;
}
get hasCompleted(): boolean {
return this._hasCompleted;
}
/**
* For public use only, for private use, directly access the member
*/
public get batchSets(): azdata.BatchSummary[] {
return this._batchSets.slice(0);
}
/**
* For public use only, for private use, directly access the member
*/
public get messages(): IQueryMessage[] {
return this._messages.slice(0);
}
// PUBLIC METHODS ======================================================
/**
* Cancels the running query, if there is one
*/
public cancelQuery(): Promise<azdata.QueryCancelResult> {
return this._queryManagementService.cancelQuery(this.uri);
}
/**
* Runs the query with the provided query
* @param input Query string to execute
*/
public runQuery(input: string, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
/**
* Runs the query by pulling the query from the document using the provided selection data
* @param input selection data
*/
public runQuery(input: azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
public runQuery(input: string | azdata.ISelectionData, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
if (types.isString(input)) {
return this.doRunQuery(input, false, runOptions);
} else {
return this.doRunQuery(input, false, runOptions);
}
}
/**
* Runs the current SQL statement by pulling the query from the document using the provided selection data
* @param input selection data
*/
public runQueryStatement(input: azdata.ISelectionData): Promise<void> {
return this.doRunQuery(input, true);
}
/**
* Implementation that runs the query with the provided query
* @param input Query string to execute
*/
private doRunQuery(input: string, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
private doRunQuery(input: azdata.ISelectionData, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void>;
private doRunQuery(input: string | azdata.ISelectionData, runCurrentStatement: boolean, runOptions?: azdata.ExecutionPlanOptions): Promise<void> {
if (this.isExecuting) {
return Promise.resolve();
}
this._planXml = new Deferred<string>();
this._batchSets = [];
this._hasCompleted = false;
this._queryStartTime = undefined;
this._queryEndTime = undefined;
this._messages = [];
if (isSelectionOrUndefined(input)) {
// Update internal state to show that we're executing the query
this._resultLineOffset = input ? input.startLine : 0;
this._resultColumnOffset = input ? input.startColumn : 0;
this._isExecuting = true;
this._totalElapsedMilliseconds = 0;
// TODO issue #228 add statusview callbacks here
this._onQueryStart.fire();
// Send the request to execute the query
return runCurrentStatement
? this._queryManagementService.runQueryStatement(this.uri, input.startLine, input.startColumn).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e))
: this._queryManagementService.runQuery(this.uri, input, runOptions).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
} else {
// Update internal state to show that we're executing the query
this._isExecuting = true;
this._totalElapsedMilliseconds = 0;
this._onQueryStart.fire();
return this._queryManagementService.runQueryString(this.uri, input).then(() => this.handleSuccessRunQueryResult(), e => this.handleFailureRunQueryResult(e));
}
}
private handleSuccessRunQueryResult() {
// this isn't exact, but its the best we can do
this._queryStartTime = new Date();
// The query has started, so lets fire up the result pane
if (!this.registered) {
this.registered = true;
this._queryManagementService.registerRunner(this, this.uri);
}
}
private handleFailureRunQueryResult(error: any) {
// Attempting to launch the query failed, show the error message
const eol = getEolString(this._textResourcePropertiesService, this.uri);
if (error instanceof Error) {
error = error.message;
}
let message = nls.localize('query.ExecutionFailedError', "Execution failed due to an unexpected error: {0}\t{1}", eol, error);
this.handleMessage(<azdata.QueryExecuteMessageParams>{
ownerUri: this.uri,
message: {
isError: true,
message: message
}
});
this.handleQueryComplete(<azdata.QueryExecuteCompleteNotificationResult>{ ownerUri: this.uri });
}
/**
* Handle a QueryComplete from the service layer
*/
public handleQueryComplete(result: azdata.QueryExecuteCompleteNotificationResult): void {
// this also isn't exact but its the best we can do
this._queryEndTime = new Date();
// Store the batch sets we got back as a source of "truth"
this._isExecuting = false;
this._hasCompleted = true;
this._batchSets = result.batchSummaries ? result.batchSummaries : [];
this._batchSets.map(batch => {
if (batch.selection) {
batch.selection.startLine += this._resultLineOffset;
batch.selection.startColumn += this._resultColumnOffset;
batch.selection.endLine += this._resultLineOffset;
batch.selection.endColumn += this._resultColumnOffset;
}
});
let timeStamp = Utils.parseNumAsTimeString(this._totalElapsedMilliseconds);
// We're done with this query so shut down any waiting mechanisms
let message = {
message: nls.localize('query.message.executionTime', "Total execution time: {0}", timeStamp),
isError: false,
time: undefined
};
this._messages.push(message);
this._onQueryEnd.fire(timeStamp);
this._onMessage.fire(message);
}
/**
* Handle a BatchStart from the service layer
*/
public handleBatchStart(result: azdata.QueryExecuteBatchNotificationParams): void {
let batch = result.batchSummary;
// Recalculate the start and end lines, relative to the result line offset
if (batch.selection) {
batch.selection.startLine += this._resultLineOffset;
batch.selection.startColumn += this._resultColumnOffset;
batch.selection.endLine += this._resultLineOffset;
batch.selection.endColumn += this._resultColumnOffset;
}
// Set the result sets as an empty array so that as result sets complete we can add to the list
batch.resultSetSummaries = [];
// Store the batch
this._batchSets[batch.id] = batch;
let message = {
// account for index by 1
message: nls.localize('query.message.startQuery', "Started executing query at Line {0}", batch.selection.startLine + 1),
time: new Date(batch.executionStart).toLocaleTimeString(),
selection: batch.selection,
isError: false
};
this._messages.push(message);
this._onMessage.fire(message);
this._onBatchStart.fire(batch);
}
/**
* Handle a BatchComplete from the service layer
*/
public handleBatchComplete(result: azdata.QueryExecuteBatchNotificationParams): void {
let batch: azdata.BatchSummary = result.batchSummary;
// Store the batch again to get the rest of the data
this._batchSets[batch.id] = batch;
let executionTime = <number>(Utils.parseTimeString(batch.executionElapsed) || 0);
this._totalElapsedMilliseconds += executionTime;
if (executionTime > 0) {
// send a time message in the format used for query complete
this.sendBatchTimeMessage(batch.id, Utils.parseNumAsTimeString(executionTime));
}
this._onBatchEnd.fire(batch);
}
/**
* Handle a ResultSetComplete from the service layer
*/
public handleResultSetAvailable(result: azdata.QueryExecuteResultSetNotificationParams): void {
if (result && result.resultSetSummary) {
let resultSet = result.resultSetSummary;
let batchSet: azdata.BatchSummary;
if (!resultSet.batchId) {
// Missing the batchId or processing batchId==0. In this case, default to always using the first batch in the list
// or create one in the case the DMP extension didn't obey the contract perfectly
if (this._batchSets.length > 0) {
batchSet = this._batchSets[0];
} else {
batchSet = <azdata.BatchSummary><unknown>{
id: 0,
selection: undefined,
hasError: false,
resultSetSummaries: []
};
this._batchSets[0] = batchSet;
}
} else {
batchSet = this._batchSets[resultSet.batchId];
}
// handle getting queryPlanxml if we need too
// check if this result has show plan, this needs work, it won't work for any other provider
let hasShowPlan = !!find(result.resultSetSummary.columnInfo, e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
if (hasShowPlan) {
this._isQueryPlan = true;
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => {
if (e.resultSubset.rows) {
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue);
}
}).catch((e) => this._logService.error(e));
}
// we will just ignore the set if we already have it
// ideally this should never happen
if (batchSet && !batchSet.resultSetSummaries[resultSet.id]) {
// Store the result set in the batch and emit that a result set has completed
batchSet.resultSetSummaries[resultSet.id] = resultSet;
this._onResultSet.fire(resultSet);
}
}
}
public handleResultSetUpdated(result: azdata.QueryExecuteResultSetNotificationParams): void {
if (result && result.resultSetSummary) {
let resultSet = result.resultSetSummary;
let batchSet: azdata.BatchSummary;
batchSet = this._batchSets[resultSet.batchId];
// handle getting queryPlanxml if we need too
// check if this result has show plan, this needs work, it won't work for any other provider
let hasShowPlan = !!find(result.resultSetSummary.columnInfo, e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
if (hasShowPlan) {
this._isQueryPlan = true;
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => {
if (e.resultSubset.rows) {
let planXmlString = e.resultSubset.rows[0][0].displayValue;
this._planXml.resolve(e.resultSubset.rows[0][0].displayValue);
// fire query plan available event if execution is completed
if (result.resultSetSummary.complete) {
this._onQueryPlanAvailable.fire({
providerId: mssqlProviderName,
fileUri: result.ownerUri,
planXml: planXmlString
});
}
}
}).catch((e) => this._logService.error(e));
}
if (batchSet) {
// Store the result set in the batch and emit that a result set has completed
batchSet.resultSetSummaries[resultSet.id] = resultSet;
this._onResultSetUpdate.fire(resultSet);
}
}
}
/**
* Handle a Mssage from the service layer
*/
public handleMessage(obj: azdata.QueryExecuteMessageParams): void {
let message = obj.message;
message.time = new Date(message.time!).toLocaleTimeString();
this._messages.push(message);
// Send the message to the results pane
this._onMessage.fire(message);
}
/**
* Get more data rows from the current resultSets from the service layer
*/
public getQueryRows(rowStart: number, numberOfRows: number, batchIndex: number, resultSetIndex: number): Promise<azdata.QueryExecuteSubsetResult> {
let rowData: azdata.QueryExecuteSubsetParams = <azdata.QueryExecuteSubsetParams>{
ownerUri: this.uri,
resultSetIndex: resultSetIndex,
rowsCount: numberOfRows,
rowsStartIndex: rowStart,
batchIndex: batchIndex
};
return this._queryManagementService.getQueryRows(rowData).then(r => r, error => {
// this._notificationService.notify({
// severity: Severity.Error,
// message: nls.localize('query.gettingRowsFailedError', 'Something went wrong getting more rows: {0}', error)
// });
return error;
});
}
/*
* Handle a session ready event for Edit Data
*/
public initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): Promise<void> {
// Update internal state to show that we're executing the query
this._isExecuting = true;
this._totalElapsedMilliseconds = 0;
// TODO issue #228 add statusview callbacks here
return this._queryManagementService.initializeEdit(ownerUri, schemaName, objectName, objectType, rowLimit, queryString).then(result => {
// The query has started, so lets fire up the result pane
this._onQueryStart.fire();
this._queryManagementService.registerRunner(this, ownerUri);
}, error => {
// Attempting to launch the query failed, show the error message
// TODO issue #228 add statusview callbacks here
this._isExecuting = false;
this._notificationService.error(nls.localize('query.initEditExecutionFailed', "Initialize edit data session failed: ") + error);
});
}
/**
* Retrieves a number of rows from an edit session
* @param rowStart The index of the row to start returning (inclusive)
* @param numberOfRows The number of rows to return
*/
public getEditRows(rowStart: number, numberOfRows: number): Promise<azdata.EditSubsetResult> {
const self = this;
let rowData: azdata.EditSubsetParams = {
ownerUri: this.uri,
rowCount: numberOfRows,
rowStartIndex: rowStart
};
return new Promise<azdata.EditSubsetResult>((resolve, reject) => {
self._queryManagementService.getEditRows(rowData).then(result => {
if (!result.hasOwnProperty('rowCount')) {
let error = `Nothing returned from subset query`;
self._notificationService.notify({
severity: Severity.Error,
message: error
});
reject(error);
}
resolve(result);
}, error => {
// let errorMessage = nls.localize('query.moreRowsFailedError', "Something went wrong getting more rows:");
// self._notificationService.notify({
// severity: Severity.Error,
// message: `${errorMessage} ${error}`
// });
reject(error);
});
});
}
public handleEditSessionReady(ownerUri: string, success: boolean, message: string): void {
this._onEditSessionReady.fire({ ownerUri, success, message });
}
public updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult> {
return this._queryManagementService.updateCell(ownerUri, rowId, columnId, newValue);
}
public commitEdit(ownerUri: string): Promise<void> {
return this._queryManagementService.commitEdit(ownerUri);
}
public createRow(ownerUri: string): Promise<azdata.EditCreateRowResult> {
return this._queryManagementService.createRow(ownerUri).then(result => {
return result;
});
}
public deleteRow(ownerUri: string, rowId: number): Promise<void> {
return this._queryManagementService.deleteRow(ownerUri, rowId);
}
public revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult> {
return this._queryManagementService.revertCell(ownerUri, rowId, columnId).then(result => {
return result;
});
}
public revertRow(ownerUri: string, rowId: number): Promise<void> {
return this._queryManagementService.revertRow(ownerUri, rowId);
}
public disposeEdit(ownerUri: string): Promise<void> {
return this._queryManagementService.disposeEdit(ownerUri);
}
/**
* Disposes the Query from the service client
*/
public async disposeQuery(): Promise<void> {
await this._queryManagementService.disposeQuery(this.uri);
this.dispose();
}
public dispose() {
this._batchSets = undefined!;
super.dispose();
}
get totalElapsedMilliseconds(): number {
return this._totalElapsedMilliseconds;
}
/**
* 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
*/
async copyResults(selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): Promise<void> {
let provider = this.getGridDataProvider(batchId, resultId);
return provider.copyResults(selection, includeHeaders);
}
public getColumnHeaders(batchId: number, resultId: number, range: Slick.Range): string[] | undefined {
let headers: string[] | undefined = undefined;
let batchSummary: azdata.BatchSummary = this._batchSets[batchId];
if (batchSummary !== undefined) {
let resultSetSummary = batchSummary.resultSetSummaries[resultId];
headers = resultSetSummary.columnInfo.slice(range.fromCell, range.toCell + 1).map((info, i) => {
return info.columnName;
});
}
return headers;
}
private sendBatchTimeMessage(batchId: number, executionTime: string): void {
// get config copyRemoveNewLine option from vscode config
let showBatchTime = this._configurationService.getValue<boolean>('sql.showBatchTime');
if (showBatchTime) {
let message: IQueryMessage = {
batchId: batchId,
message: nls.localize('elapsedBatchTime', "Batch execution time: {0}", executionTime),
time: undefined,
isError: false
};
this._messages.push(message);
// Send the message to the results pane
this._onMessage.fire(message);
}
}
public serializeResults(batchId: number, resultSetId: number, format: SaveFormat, selection: Slick.Range[]) {
return this.instantiationService.createInstance(ResultSerializer).saveResults(this.uri, { selection, format, batchIndex: batchId, resultSetNumber: resultSetId });
}
public getGridDataProvider(batchId: number, resultSetId: number): IGridDataProvider {
return this.instantiationService.createInstance(QueryGridDataProvider, this, batchId, resultSetId);
}
public notifyVisualizeRequested(batchId: number, resultSetId: number): void {
let result: azdata.ResultSetSummary = {
batchId: batchId,
id: resultSetId,
columnInfo: this.batchSets[batchId].resultSetSummaries[resultSetId].columnInfo,
complete: true,
rowCount: this.batchSets[batchId].resultSetSummaries[resultSetId].rowCount
};
this._onVisualize.fire(result);
}
}
export class QueryGridDataProvider implements IGridDataProvider {
constructor(
private queryRunner: QueryRunner,
private batchId: number,
private resultSetId: number,
@INotificationService private _notificationService: INotificationService,
@IClipboardService private _clipboardService: IClipboardService,
@IConfigurationService private _configurationService: IConfigurationService,
@ITextResourcePropertiesService private _textResourcePropertiesService: ITextResourcePropertiesService
) {
}
getRowData(rowStart: number, numberOfRows: number): Promise<azdata.QueryExecuteSubsetResult> {
return this.queryRunner.getQueryRows(rowStart, numberOfRows, this.batchId, this.resultSetId);
}
copyResults(selection: Slick.Range[], includeHeaders?: boolean): Promise<void> {
return this.copyResultsAsync(selection, includeHeaders);
}
private async copyResultsAsync(selection: Slick.Range[], includeHeaders?: boolean): Promise<void> {
try {
let results = await getResultsString(this, selection, includeHeaders);
await this._clipboardService.writeText(results);
} catch (error) {
this._notificationService.error(nls.localize('copyFailed', "Copy failed with error {0}", getErrorMessage(error)));
}
}
getEolString(): string {
return getEolString(this._textResourcePropertiesService, this.queryRunner.uri);
}
shouldIncludeHeaders(includeHeaders: boolean): boolean {
return shouldIncludeHeaders(includeHeaders, this._configurationService);
}
shouldRemoveNewLines(): boolean {
return shouldRemoveNewLines(this._configurationService);
}
getColumnHeaders(range: Slick.Range): string[] | undefined {
return this.queryRunner.getColumnHeaders(this.batchId, this.resultSetId, range);
}
get canSerialize(): boolean {
return true;
}
serializeResults(format: SaveFormat, selection: Slick.Range[]): Promise<void> {
return this.queryRunner.serializeResults(this.batchId, this.resultSetId, format, selection);
}
}
export function getEolString(textResourcePropertiesService: ITextResourcePropertiesService, uri: string): string {
return textResourcePropertiesService.getEOL(URI.parse(uri), 'sql');
}
export function shouldIncludeHeaders(includeHeaders: boolean, configurationService: IConfigurationService): boolean {
if (includeHeaders !== undefined) {
// Respect the value explicity passed into the method
return includeHeaders;
}
// else get config option from vscode config
includeHeaders = configurationService.getValue<boolean>('sql.copyIncludeHeaders');
return !!includeHeaders;
}
export function shouldRemoveNewLines(configurationService: IConfigurationService): boolean {
// get config copyRemoveNewLine option from vscode config
let removeNewLines = configurationService.getValue<boolean>('sql.copyRemoveNewLine');
return !!removeNewLines;
}
function isSelectionOrUndefined(input: string | azdata.ISelectionData | undefined): input is azdata.ISelectionData | undefined {
return types.isObject(input) || types.isUndefinedOrNull(input);
}

View File

@@ -1,116 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel';
import QueryRunner from 'sql/platform/query/common/queryRunner';
import * as azdata from 'azdata';
import { QueryEditorInput } from 'sql/workbench/contrib/query/common/queryEditorInput';
import { Event } from 'vs/base/common/event';
import { QueryInfo } from 'sql/platform/query/common/queryModelService';
import { DataService } from 'sql/workbench/contrib/grid/common/dataService';
export class TestQueryModelService implements IQueryModelService {
_serviceBrand: any;
onRunQueryUpdate: Event<string>;
getQueryRunner(uri: string): QueryRunner {
throw new Error('Method not implemented.');
}
getConfig(): Promise<{ [key: string]: any; }> {
throw new Error('Method not implemented.');
}
getShortcuts(): Promise<any> {
throw new Error('Method not implemented.');
}
getQueryRows(uri: string, rowStart: number, numberOfRows: number, batchId: number, resultId: number): Promise<azdata.ResultSetSubset> {
throw new Error('Method not implemented.');
}
runQuery(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput, runOptions?: azdata.ExecutionPlanOptions): void {
throw new Error('Method not implemented.');
}
runQueryStatement(uri: string, selection: azdata.ISelectionData, queryInput: QueryEditorInput): void {
throw new Error('Method not implemented.');
}
runQueryString(uri: string, selection: string, queryInput: QueryEditorInput) {
throw new Error('Method not implemented.');
}
cancelQuery(input: string | QueryRunner): void {
throw new Error('Method not implemented.');
}
disposeQuery(uri: string): void {
throw new Error('Method not implemented.');
}
isRunningQuery(uri: string): boolean {
throw new Error('Method not implemented.');
}
getDataService(uri: string): DataService {
throw new Error('Method not implemented.');
}
refreshResultsets(uri: string): void {
throw new Error('Method not implemented.');
}
sendGridContentEvent(uri: string, eventName: string): void {
throw new Error('Method not implemented.');
}
resizeResultsets(uri: string): void {
throw new Error('Method not implemented.');
}
onLoaded(uri: string): void {
throw new Error('Method not implemented.');
}
copyResults(uri: string, selection: Slick.Range[], batchId: number, resultId: number, includeHeaders?: boolean): void {
throw new Error('Method not implemented.');
}
showWarning(uri: string, message: string): void {
throw new Error('Method not implemented.');
}
showError(uri: string, message: string): void {
throw new Error('Method not implemented.');
}
showCommitError(error: string): void {
throw new Error('Method not implemented.');
}
get onRunQueryStart(): Event<string> {
return Event.None;
}
get onRunQueryComplete(): Event<string> {
return Event.None;
}
get onQueryEvent(): Event<IQueryEvent> {
throw new Error('Method not implemented.');
}
initializeEdit(ownerUri: string, schemaName: string, objectName: string, objectType: string, rowLimit: number, queryString: string): void {
throw new Error('Method not implemented.');
}
disposeEdit(ownerUri: string): Promise<void> {
throw new Error('Method not implemented.');
}
updateCell(ownerUri: string, rowId: number, columnId: number, newValue: string): Promise<azdata.EditUpdateCellResult> {
throw new Error('Method not implemented.');
}
commitEdit(ownerUri: any): Promise<void> {
throw new Error('Method not implemented.');
}
createRow(ownerUri: string): Promise<azdata.EditCreateRowResult> {
throw new Error('Method not implemented.');
}
deleteRow(ownerUri: string, rowId: number): Promise<void> {
throw new Error('Method not implemented.');
}
revertCell(ownerUri: string, rowId: number, columnId: number): Promise<azdata.EditRevertCellResult> {
throw new Error('Method not implemented.');
}
revertRow(ownerUri: string, rowId: number): Promise<void> {
throw new Error('Method not implemented.');
}
getEditRows(ownerUri: string, rowStart: number, numberOfRows: number): Promise<azdata.EditSubsetResult> {
throw new Error('Method not implemented.');
}
_getQueryInfo(uri: string): QueryInfo {
throw new Error('Method not implemented.');
}
onEditSessionReady: Event<azdata.EditSessionReadyParams>;
}

View File

@@ -1,34 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { generateUuid } from 'vs/base/common/uuid';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
export enum QueryStatus {
Succeeded = 0,
Failed = 1,
Nothing = 2
}
/**
* Contains information about a query that was ran
*/
export class QueryHistoryInfo {
public database: string;
public status: QueryStatus;
public readonly id = generateUuid();
constructor(
public queryText: string,
public connectionProfile: IConnectionProfile,
public startTime: Date,
status?: QueryStatus) {
this.database = connectionProfile ? connectionProfile.databaseName : '';
this.status = status;
}
}

View File

@@ -1,55 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { QueryHistoryInfo } from 'sql/platform/queryHistory/common/queryHistoryInfo';
import { Event } from 'vs/base/common/event';
export const SERVICE_ID = 'queryHistoryService';
export const IQueryHistoryService = createDecorator<IQueryHistoryService>(SERVICE_ID);
/**
* Service that collects the results of executed queries
*/
export interface IQueryHistoryService {
_serviceBrand: any;
/**
* Event fired whenever the collection of stored QueryHistoryInfo's is updated
*/
onInfosUpdated: Event<QueryHistoryInfo[]>;
/**
* Event fired whenever the Query History capture state has changed
*/
onQueryHistoryCaptureChanged: Event<boolean>;
/**
* Whether Query History capture is currently enabled
*/
readonly captureEnabled: boolean;
/**
* Gets the current list of Query History Info objects that have been collected
*/
getQueryHistoryInfos(): QueryHistoryInfo[];
/**
* Deletes all QueryHistoryInfo's from the collection that have the same id as the specified one
* @param info The QueryHistoryInfo to delete
*/
deleteQueryHistoryInfo(info: QueryHistoryInfo): void;
/**
* Clears all Query History - removing all collected items
*/
clearQueryHistory(): void;
/**
* Toggles whether Query History capture is enabled
*/
toggleCaptureEnabled(): Promise<void>;
/**
* Starts the Query History Service
*/
start(): void;
}

View File

@@ -1,130 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQueryHistoryService } from 'sql/platform/queryHistory/common/queryHistoryService.ts';
import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel';
import { IModelService } from 'vs/editor/common/services/modelService';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { QueryHistoryInfo, QueryStatus } from 'sql/platform/queryHistory/common/queryHistoryInfo';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { find } from 'vs/base/common/arrays';
/**
* Service that collects the results of executed queries
*/
export class QueryHistoryService extends Disposable implements IQueryHistoryService {
_serviceBrand: any;
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _infos: QueryHistoryInfo[] = [];
private _onInfosUpdated: Emitter<QueryHistoryInfo[]> = new Emitter<QueryHistoryInfo[]>();
private _onQueryHistoryCaptureChanged: Emitter<boolean> = new Emitter<boolean>();
private _captureEnabled;
// EVENTS //////////////////////////////////////////////////////////////
public get onInfosUpdated(): Event<QueryHistoryInfo[]> { return this._onInfosUpdated.event; }
public get onQueryHistoryCaptureChanged(): Event<boolean> { return this._onQueryHistoryCaptureChanged.event; }
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
@IQueryModelService _queryModelService: IQueryModelService,
@IModelService _modelService: IModelService,
@IConnectionManagementService _connectionManagementService: IConnectionManagementService,
@IConfigurationService private _configurationService: IConfigurationService
) {
super();
this._captureEnabled = !!this._configurationService.getValue<boolean>('queryHistory.captureEnabled');
this._register(this._configurationService.onDidChangeConfiguration((e: IConfigurationChangeEvent) => {
if (find(e.affectedKeys, x => x === 'queryHistory.captureEnabled')) {
this.updateCaptureEnabled();
}
}));
this._register(_queryModelService.onQueryEvent((e: IQueryEvent) => {
if (this._captureEnabled && e.type === 'queryStop') {
const uri: URI = URI.parse(e.uri);
// VS Range is 1 based so offset values by 1. The endLine we get back from SqlToolsService is incremented
// by 1 from the original input range sent in as well so take that into account and don't modify
const text: string = e.queryInfo.selection && e.queryInfo.selection.length > 0 ?
_modelService.getModel(uri).getValueInRange(new Range(
e.queryInfo.selection[0].startLine + 1,
e.queryInfo.selection[0].startColumn + 1,
e.queryInfo.selection[0].endLine,
e.queryInfo.selection[0].endColumn + 1)) :
// If no specific selection get the entire text
_modelService.getModel(uri).getValue();
const newInfo = new QueryHistoryInfo(text, _connectionManagementService.getConnectionProfile(e.uri), new Date(), QueryStatus.Succeeded);
// icon as required (for now logic is if any message has error query has error)
let error: boolean = false;
e.queryInfo.messages.forEach(x => error = error || x.isError);
if (error) {
newInfo.status = QueryStatus.Failed;
}
// Append new node to beginning of array so the newest ones are at the top
this._infos.unshift(newInfo);
this._onInfosUpdated.fire(this._infos);
}
}));
}
/**
* Whether Query History capture is currently enabled
*/
public get captureEnabled(): boolean {
return this._captureEnabled;
}
/**
* Gets all the current query history infos
*/
public getQueryHistoryInfos(): QueryHistoryInfo[] {
return this._infos;
}
/**
* Deletes infos from the cache with the same ID as the given QueryHistoryInfo
* @param info The QueryHistoryInfo to delete
*/
public deleteQueryHistoryInfo(info: QueryHistoryInfo): void {
this._infos = this._infos.filter(i => i.id !== info.id);
this._onInfosUpdated.fire(this._infos);
}
/**
* Clears all infos from the cache
*/
public clearQueryHistory(): void {
this._infos = [];
this._onInfosUpdated.fire(this._infos);
}
public async toggleCaptureEnabled(): Promise<void> {
const captureEnabled = !!this._configurationService.getValue<boolean>('queryHistory.captureEnabled');
await this._configurationService.updateValue('queryHistory.captureEnabled', !captureEnabled);
}
private updateCaptureEnabled(): void {
const currentCaptureEnabled = this._captureEnabled;
this._captureEnabled = !!this._configurationService.getValue<boolean>('queryHistory.captureEnabled');
if (currentCaptureEnabled !== this._captureEnabled) {
this._onQueryHistoryCaptureChanged.fire(this._captureEnabled);
}
}
/**
* Method to force initialization of the service so that it can start tracking query events
*/
public start(): void {
}
}

View File

@@ -1,363 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as types from 'vs/base/common/types';
import * as azdata from 'azdata';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { IRestoreService, IRestoreDialogController, TaskExecutionMode } from 'sql/platform/restore/common/restoreService';
import { OptionsDialog } from 'sql/workbench/browser/modal/optionsDialog';
import { RestoreDialog } from 'sql/workbench/contrib/restore/browser/restoreDialog';
import * as ConnectionConstants from 'sql/platform/connection/common/constants';
import { MssqlRestoreInfo } from 'sql/platform/restore/common/mssqlRestoreInfo';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ProviderConnectionInfo } from 'sql/platform/connection/common/providerConnectionInfo';
import * as Utils from 'sql/platform/connection/common/utils';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { ITaskService } from 'sql/platform/tasks/common/tasksService';
import { TaskStatus, TaskNode } from 'sql/platform/tasks/common/tasksNode';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { invalidProvider } from 'sql/base/common/errors';
import { ILogService } from 'vs/platform/log/common/log';
import { find } from 'vs/base/common/arrays';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
export class RestoreService implements IRestoreService {
public _serviceBrand: undefined;
private _providers: { [handle: string]: azdata.RestoreProvider; } = Object.create(null);
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService
) {
}
/**
* Gets restore config Info
*/
getRestoreConfigInfo(connectionUri: string): Thenable<azdata.RestoreConfigInfo> {
return new Promise<azdata.RestoreConfigInfo>((resolve, reject) => {
const providerResult = this.getProvider(connectionUri);
if (providerResult) {
providerResult.provider.getRestoreConfigInfo(connectionUri).then(result => {
resolve(result);
}, error => {
reject(error);
});
} else {
reject(invalidProvider());
}
});
}
/**
* Restore a data source using a backup file or database
*/
restore(connectionUri: string, restoreInfo: azdata.RestoreInfo): Thenable<azdata.RestoreResponse> {
return new Promise<azdata.RestoreResponse>((resolve, reject) => {
const providerResult = this.getProvider(connectionUri);
if (providerResult) {
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.RestoreRequested)
.withAdditionalProperties({
provider: providerResult.providerName
}).send();
providerResult.provider.restore(connectionUri, restoreInfo).then(result => {
resolve(result);
}, error => {
reject(error);
});
} else {
reject(invalidProvider);
}
});
}
private getProvider(connectionUri: string): { provider: azdata.RestoreProvider, providerName: string } {
let providerId: string = this._connectionService.getProviderIdFromUri(connectionUri);
if (providerId) {
return { provider: this._providers[providerId], providerName: providerId };
} else {
return undefined;
}
}
/**
* Gets restore plan to do the restore operation on a database
*/
getRestorePlan(connectionUri: string, restoreInfo: azdata.RestoreInfo): Thenable<azdata.RestorePlanResponse> {
return new Promise<azdata.RestorePlanResponse>((resolve, reject) => {
const providerResult = this.getProvider(connectionUri);
if (providerResult) {
providerResult.provider.getRestorePlan(connectionUri, restoreInfo).then(result => {
resolve(result);
}, error => {
reject(error);
});
} else {
reject(invalidProvider);
}
});
}
/**
* Cancels a restore plan
*/
cancelRestorePlan(connectionUri: string, restoreInfo: azdata.RestoreInfo): Thenable<boolean> {
return new Promise<boolean>((resolve, reject) => {
const providerResult = this.getProvider(connectionUri);
if (providerResult) {
providerResult.provider.cancelRestorePlan(connectionUri, restoreInfo).then(result => {
resolve(result);
}, error => {
reject(error);
});
} else {
reject(invalidProvider);
}
});
}
/**
* Register a disaster recovery provider
*/
public registerProvider(providerId: string, provider: azdata.RestoreProvider): void {
this._providers[providerId] = provider;
}
}
export class RestoreDialogController implements IRestoreDialogController {
_serviceBrand: undefined;
private _restoreDialogs: { [provider: string]: RestoreDialog | OptionsDialog } = {};
private _currentProvider: string;
private _ownerUri: string;
private _sessionId: string;
private readonly _restoreFeature = 'Restore';
private readonly _restoreTaskName: string = 'Restore Database';
private readonly _restoreCompleted: string = 'Completed';
private _optionValues: { [optionName: string]: any } = {};
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@IRestoreService private _restoreService: IRestoreService,
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@ITaskService private _taskService: ITaskService,
@ILogService private _logService: ILogService,
) {
}
private handleOnRestore(isScriptOnly: boolean = false): void {
let restoreOption = this.setRestoreOption();
if (isScriptOnly) {
restoreOption.taskExecutionMode = TaskExecutionMode.script;
} else {
restoreOption.taskExecutionMode = TaskExecutionMode.executeAndScript;
}
this._restoreService.restore(this._ownerUri, restoreOption).then(result => {
const self = this;
let connectionProfile = self._connectionService.getConnectionProfile(self._ownerUri);
let activeNode = self._objectExplorerService.getObjectExplorerNode(connectionProfile);
this._taskService.onTaskComplete(async response => {
if (result.taskId === response.id && this.isSuccessfulRestore(response) && activeNode) {
try {
await self._objectExplorerService.refreshTreeNode(activeNode.getSession(), activeNode);
await self._objectExplorerService.getServerTreeView().refreshTree();
} catch (e) {
this._logService.error(e);
}
}
});
let restoreDialog = this._restoreDialogs[this._currentProvider];
restoreDialog.close();
});
}
private isSuccessfulRestore(response: TaskNode): boolean {
return (response.taskName === this._restoreTaskName &&
response.message === this._restoreCompleted &&
(response.status === TaskStatus.Succeeded ||
response.status === TaskStatus.SucceededWithWarning) &&
(response.taskExecutionMode === TaskExecutionMode.execute ||
response.taskExecutionMode === TaskExecutionMode.executeAndScript));
}
private handleMssqlOnValidateFile(overwriteTargetDatabase: boolean = false): void {
let restoreDialog = this._restoreDialogs[this._currentProvider] as RestoreDialog;
this._restoreService.getRestorePlan(this._ownerUri, this.setRestoreOption(overwriteTargetDatabase)).then(restorePlanResponse => {
this._sessionId = restorePlanResponse.sessionId;
if (restorePlanResponse.errorMessage) {
restoreDialog.onValidateResponseFail(restorePlanResponse.errorMessage);
} else {
restoreDialog.removeErrorMessage();
restoreDialog.viewModel.onRestorePlanResponse(restorePlanResponse);
}
if (restorePlanResponse.canRestore && !this.isEmptyBackupset()) {
restoreDialog.enableRestoreButton(true);
} else {
restoreDialog.enableRestoreButton(false);
}
}, error => {
restoreDialog.showError(error);
});
}
/**
* Temporary fix for bug #2506: Restore button not disabled when there's not backup set to restore
* Will remove this function once there is a fix in the service (bug #2572)
*/
private isEmptyBackupset(): boolean {
let restoreDialog = this._restoreDialogs[this._currentProvider] as RestoreDialog;
if (!types.isUndefinedOrNull(restoreDialog.viewModel.selectedBackupSets) && restoreDialog.viewModel.selectedBackupSets.length === 0) {
return true;
}
return false;
}
private getMssqlRestoreConfigInfo(): Promise<void> {
return new Promise<void>((resolve, reject) => {
let restoreDialog = this._restoreDialogs[this._currentProvider] as RestoreDialog;
this._restoreService.getRestoreConfigInfo(this._ownerUri).then(restoreConfigInfo => {
restoreDialog.viewModel.updateOptionWithConfigInfo(restoreConfigInfo.configInfo);
resolve();
}, error => {
restoreDialog.showError(error);
reject(error);
});
});
}
private setRestoreOption(overwriteTargetDatabase: boolean = false): azdata.RestoreInfo {
let restoreInfo = undefined;
let providerId: string = this.getCurrentProviderId();
if (providerId === ConnectionConstants.mssqlProviderName) {
restoreInfo = new MssqlRestoreInfo();
if (this._sessionId) {
restoreInfo.sessionId = this._sessionId;
}
let restoreDialog = this._restoreDialogs[providerId] as RestoreDialog;
restoreInfo.backupFilePaths = restoreDialog.viewModel.filePath;
restoreInfo.readHeaderFromMedia = restoreDialog.viewModel.readHeaderFromMedia;
restoreInfo.selectedBackupSets = restoreDialog.viewModel.selectedBackupSets;
restoreInfo.sourceDatabaseName = restoreDialog.viewModel.sourceDatabaseName;
if (restoreDialog.viewModel.targetDatabaseName) {
restoreInfo.targetDatabaseName = restoreDialog.viewModel.targetDatabaseName;
}
restoreInfo.overwriteTargetDatabase = overwriteTargetDatabase;
// Set other restore options
restoreDialog.viewModel.getRestoreAdvancedOptions(restoreInfo.options);
} else {
restoreInfo = { options: this._optionValues };
}
return restoreInfo;
}
private getRestoreOption(): azdata.ServiceOption[] {
let options: azdata.ServiceOption[] = [];
let providerId: string = this.getCurrentProviderId();
let providerCapabilities = this._capabilitiesService.getLegacyCapabilities(providerId);
if (providerCapabilities) {
let restoreMetadataProvider = find(providerCapabilities.features, f => f.featureName === this._restoreFeature);
if (restoreMetadataProvider) {
options = restoreMetadataProvider.optionsMetadata;
}
}
return options;
}
private handleOnClose(): void {
this._connectionService.disconnect(this._ownerUri).catch((e) => this._logService.error(e));
}
private handleOnCancel(): void {
let restoreInfo = new MssqlRestoreInfo();
restoreInfo.sessionId = this._sessionId;
this._restoreService.cancelRestorePlan(this._ownerUri, restoreInfo).then(() => {
this._connectionService.disconnect(this._ownerUri);
});
}
public showDialog(connection: IConnectionProfile): Promise<void> {
return new Promise<void>((resolve, reject) => {
let result: void;
this._ownerUri = this._connectionService.getConnectionUri(connection)
+ ProviderConnectionInfo.idSeparator
+ Utils.ConnectionUriRestoreIdAttributeName
+ ProviderConnectionInfo.nameValueSeparator
+ '0';
if (!this._connectionService.isConnected(this._ownerUri)) {
this._connectionService.connect(connection, this._ownerUri).then(connectionResult => {
this._sessionId = null;
this._currentProvider = this.getCurrentProviderId();
if (!this._restoreDialogs[this._currentProvider]) {
let newRestoreDialog: RestoreDialog | OptionsDialog = undefined;
if (this._currentProvider === ConnectionConstants.mssqlProviderName) {
let provider = this._currentProvider;
newRestoreDialog = this._instantiationService.createInstance(RestoreDialog, this.getRestoreOption());
newRestoreDialog.onCancel(() => this.handleOnCancel());
newRestoreDialog.onRestore((isScriptOnly) => this.handleOnRestore(isScriptOnly));
newRestoreDialog.onValidate((overwriteTargetDatabase) => this.handleMssqlOnValidateFile(overwriteTargetDatabase));
newRestoreDialog.onDatabaseListFocused(() => this.fetchDatabases(provider));
} else {
newRestoreDialog = this._instantiationService.createInstance(
OptionsDialog, 'Restore database - ' + connection.serverName + ':' + connection.databaseName, 'RestoreOptions', undefined);
newRestoreDialog.onOk(() => this.handleOnRestore());
}
newRestoreDialog.onCloseEvent(() => this.handleOnClose());
newRestoreDialog.render();
this._restoreDialogs[this._currentProvider] = newRestoreDialog;
}
if (this._currentProvider === ConnectionConstants.mssqlProviderName) {
let restoreDialog = this._restoreDialogs[this._currentProvider] as RestoreDialog;
restoreDialog.viewModel.resetRestoreOptions(connection.databaseName);
this.getMssqlRestoreConfigInfo().then(() => {
restoreDialog.open(connection.serverName, this._ownerUri);
restoreDialog.validateRestore();
}, restoreConfigError => {
reject(restoreConfigError);
});
} else {
let restoreDialog = this._restoreDialogs[this._currentProvider] as OptionsDialog;
restoreDialog.open(this.getRestoreOption(), this._optionValues);
}
resolve(result);
}, error => {
reject(error);
});
}
});
}
private getCurrentProviderId(): string {
return this._connectionService.getProviderIdFromUri(this._ownerUri);
}
private fetchDatabases(provider: string): void {
this._connectionService.listDatabases(this._ownerUri).then(result => {
if (result && result.databaseNames) {
(<RestoreDialog>this._restoreDialogs[provider]).databaseListOptions = result.databaseNames;
}
});
}
}

View File

@@ -1,96 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
export class MssqlRestoreInfo implements azdata.RestoreInfo {
options: { [name: string]: any };
taskExecutionMode: azdata.TaskExecutionMode;
public constructor() {
this.options = {};
}
public get sessionId(): string {
return this.options['sessionId'];
}
public set sessionId(value: string) {
this.options['sessionId'] = value;
}
public get backupFilePaths(): string {
return this.options['backupFilePaths'];
}
public set backupFilePaths(value: string) {
this.options['backupFilePaths'] = value;
}
public get targetDatabaseName(): string {
return this.options['targetDatabaseName'];
}
public set targetDatabaseName(value: string) {
this.options['targetDatabaseName'] = value;
}
public get sourceDatabaseName(): string {
return this.options['sourceDatabaseName'];
}
public set sourceDatabaseName(value: string) {
this.options['sourceDatabaseName'] = value;
}
public get relocateDbFiles(): boolean {
return this.options['relocateDbFiles'];
}
public set relocateDbFiles(value: boolean) {
this.options['relocateDbFiles'] = value;
}
public get dataFileFolder(): string {
return this.options['dataFileFolder'];
}
public set dataFileFolder(value: string) {
this.options['dataFileFolder'] = value;
}
public get logFileFolder(): string {
return this.options['logFileFolder'];
}
public set logFileFolder(value: string) {
this.options['logFileFolder'] = value;
}
public get selectedBackupSets(): string[] {
return this.options['selectedBackupSets'];
}
public set selectedBackupSets(value: string[]) {
this.options['selectedBackupSets'] = value;
}
public get readHeaderFromMedia(): boolean {
return this.options['readHeaderFromMedia'];
}
public set readHeaderFromMedia(value: boolean) {
this.options['readHeaderFromMedia'] = value;
}
public get overwriteTargetDatabase(): boolean {
return this.options['overwriteTargetDatabase'];
}
public set overwriteTargetDatabase(value: boolean) {
this.options['overwriteTargetDatabase'] = value;
}
}

View File

@@ -1,48 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as azdata from 'azdata';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
export { TaskExecutionMode } from 'sql/platform/backup/common/backupService';
export const SERVICE_ID = 'restoreService';
export const IRestoreService = createDecorator<IRestoreService>(SERVICE_ID);
export interface IRestoreService {
_serviceBrand: undefined;
/**
* Register a disaster recovery provider
*/
registerProvider(providerId: string, provider: azdata.RestoreProvider): void;
/**
* Restore a data source using a backup file or database
*/
restore(connectionUri: string, restoreInfo: azdata.RestoreInfo): Thenable<azdata.RestoreResponse>;
/**
* Gets restore plan to do the restore operation on a database
*/
getRestorePlan(connectionUri: string, restoreInfo: azdata.RestoreInfo): Thenable<azdata.RestorePlanResponse>;
/**
* Gets restore config Info
*/
getRestoreConfigInfo(connectionUri: string): Thenable<azdata.RestoreConfigInfo>;
/**
* Cancel restore plan
*/
cancelRestorePlan(connectionUri: string, restoreInfo: azdata.RestoreInfo): Thenable<boolean>;
}
export const IRestoreDialogController = createDecorator<IRestoreDialogController>('restoreDialogService');
export interface IRestoreDialogController {
_serviceBrand: undefined;
showDialog(connection: IConnectionProfile): Promise<void>;
}

View File

@@ -1,119 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MenuRegistry, ICommandAction } from 'vs/platform/actions/common/actions';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ITaskRegistry, ITaskHandler, ITask, ITaskHandlerDescription, ITaskOptions } from 'sql/platform/tasks/common/tasks';
import * as types from 'vs/base/common/types';
import { Event, Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { createCSSRule, asCSSUrl } from 'vs/base/browser/dom';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IdGenerator } from 'vs/base/common/idGenerator';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
const ids = new IdGenerator('task-icon-');
export const TaskRegistry: ITaskRegistry = new class implements ITaskRegistry {
private _tasks = new Array<string>();
private _onTaskRegistered = new Emitter<string>();
public readonly onTaskRegistered: Event<string> = this._onTaskRegistered.event;
private taskIdToIconClassNameMap: Map<string /* task id */, string /* CSS rule */> = new Map<string, string>();
registerTask(idOrTask: string | ITask, handler?: ITaskHandler): IDisposable {
let disposable: IDisposable;
let id: string;
if (types.isString(idOrTask)) {
disposable = CommandsRegistry.registerCommand(idOrTask, handler!);
id = idOrTask;
} else {
if (idOrTask.iconClass) {
this.taskIdToIconClassNameMap.set(idOrTask.id, idOrTask.iconClass);
}
disposable = CommandsRegistry.registerCommand(idOrTask);
id = idOrTask.id;
}
this._tasks.push(id);
this._onTaskRegistered.fire(id);
return {
dispose: () => {
let index = this._tasks.indexOf(id);
if (index >= 0) {
this._tasks = this._tasks.splice(index, 1);
}
disposable.dispose();
}
};
}
getOrCreateTaskIconClassName(item: ICommandAction): string | undefined {
let iconClass: string | undefined;
if (this.taskIdToIconClassNameMap.has(item.id)) {
iconClass = this.taskIdToIconClassNameMap.get(item.id);
} else if (ThemeIcon.isThemeIcon(item.icon)) {
// TODO
} else if (item.icon?.dark) { // at the very least we need a dark icon
iconClass = ids.nextId();
createCSSRule(`.codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.light || item.icon.dark)}`);
createCSSRule(`.vs-dark .codicon.${iconClass}, .hc-black .codicon.${iconClass}`, `background-image: ${asCSSUrl(item.icon.dark)}`);
this.taskIdToIconClassNameMap.set(item.id, iconClass);
}
return iconClass;
}
getTasks(): string[] {
return this._tasks.slice(0);
}
};
export abstract class Task {
public readonly id: string;
public readonly title: string;
public readonly iconPath?: { dark: URI; light?: URI; };
private readonly _iconClass?: string;
private readonly _description?: ITaskHandlerDescription;
constructor(opts: ITaskOptions) {
this.id = opts.id;
this.title = opts.title;
if (opts.iconPath) {
this.iconPath = {
dark: URI.parse(opts.iconPath.dark),
light: opts.iconPath.light ? URI.parse(opts.iconPath.light) : undefined,
};
}
this._iconClass = opts.iconClass;
this._description = opts.description;
}
private toITask(): ITask {
return {
id: this.id,
handler: (accessor, profile, args) => this.runTask(accessor, profile, args),
description: this._description,
iconClass: this._iconClass
};
}
private toCommandAction(): ICommandAction {
return {
icon: this.iconPath,
id: this.id,
title: this.title
};
}
public registerTask(): IDisposable {
MenuRegistry.addCommand(this.toCommandAction());
return TaskRegistry.registerTask(this.toITask());
}
public abstract runTask(accessor: ServicesAccessor, profile: IConnectionProfile, args: any): void | Promise<void>;
}

View File

@@ -1,59 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import * as types from 'vs/base/common/types';
import { ILocalizedString, ICommandAction } from 'vs/platform/actions/common/actions';
import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
export interface ITaskOptions {
id: string;
title: string;
iconPath: { dark: string; light?: string; };
description?: ITaskHandlerDescription;
iconClass?: string;
}
export interface ITaskHandlerDescription {
description: string;
args: { name: string; description?: string; constraint?: types.TypeConstraint; }[];
returns?: string;
}
export interface ITaskEvent {
taskId: string;
}
export interface ITaskAction {
id: string;
title: string | ILocalizedString;
category?: string | ILocalizedString;
iconClass?: string;
iconPath?: string;
}
export interface ITaskHandler {
(accessor: ServicesAccessor, profile: IConnectionProfile, ...args: any[]): void;
}
export interface ITask {
id: string;
handler: ITaskHandler;
precondition?: ContextKeyExpr;
description?: ITaskHandlerDescription;
iconClass?: string;
}
export interface ITaskRegistry {
registerTask(id: string, command: ITaskHandler): IDisposable;
registerTask(command: ITask): IDisposable;
getTasks(): string[];
getOrCreateTaskIconClassName(item: ICommandAction): string | undefined;
onTaskRegistered: Event<string>;
}

View File

@@ -1,115 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { StopWatch } from 'vs/base/common/stopwatch';
import { generateUuid } from 'vs/base/common/uuid';
export enum TaskStatus {
NotStarted = 0,
InProgress = 1,
Succeeded = 2,
SucceededWithWarning = 3,
Failed = 4,
Canceled = 5,
Canceling = 6
}
export enum TaskExecutionMode {
execute = 0,
script = 1,
executeAndScript = 2,
}
export class TaskNode {
/**
* id for TaskNode
*/
public id: string;
/**
* string defining the type of the task - for example Backup, Restore
*/
public taskName: string;
/**
* sever name
*/
public serverName?: string;
/**
* Database Name
*/
public databaseName?: string;
/**
* Provider Name
*/
public providerName?: string;
/**
* The start time of the task
*/
public startTime: string;
/**
* The end time of the task
*/
public endTime?: string;
/**
* The timer for the task
*/
public timer: StopWatch;
/**
* Does this node have children
*/
public hasChildren: boolean;
/**
* Children of this node
*/
public children?: TaskNode[];
/**
* Task's message
*/
public message?: string;
/**
* Status of the task
*/
public status: TaskStatus;
/**
* Execution mode of task
*/
public taskExecutionMode: TaskExecutionMode;
/**
* Indicates if the task can be canceled
*/
public isCancelable: boolean;
/**
* Script of task operation
*/
public script?: string;
constructor(taskName: string, serverName?: string, databaseName?: string, taskId: string | undefined = undefined, taskExecutionMode: TaskExecutionMode = TaskExecutionMode.execute, isCancelable: boolean = true) {
this.id = taskId || generateUuid();
this.taskName = taskName;
this.serverName = serverName;
this.databaseName = databaseName;
this.timer = StopWatch.create();
this.startTime = new Date().toLocaleTimeString();
this.status = TaskStatus.InProgress;
this.hasChildren = false;
this.taskExecutionMode = taskExecutionMode;
this.isCancelable = isCancelable;
}
}

View File

@@ -1,254 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { TaskNode, TaskStatus, TaskExecutionMode } from 'sql/platform/tasks/common/tasksNode';
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event, Emitter } from 'vs/base/common/event';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { localize } from 'vs/nls';
import Severity from 'vs/base/common/severity';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { find } from 'vs/base/common/arrays';
export const SERVICE_ID = 'taskHistoryService';
export const ITaskService = createDecorator<ITaskService>(SERVICE_ID);
export interface ITaskService {
_serviceBrand: undefined;
onTaskComplete: Event<TaskNode>;
onAddNewTask: Event<TaskNode>;
handleNewTask(task: TaskNode): void;
handleTaskComplete(eventArgs: TaskStatusChangeArgs): void;
getAllTasks(): TaskNode;
getNumberOfInProgressTasks(): number;
onNewTaskCreated(handle: number, taskInfo: azdata.TaskInfo): void;
createNewTask(taskInfo: azdata.TaskInfo): void;
updateTask(taskProgressInfo: azdata.TaskProgressInfo): void;
onTaskStatusChanged(handle: number, taskProgressInfo: azdata.TaskProgressInfo): void;
cancelTask(providerId: string, taskId: string): Promise<boolean | undefined>;
/**
* Register a ObjectExplorer provider
*/
registerProvider(providerId: string, provider: azdata.TaskServicesProvider): void;
}
export interface TaskStatusChangeArgs {
taskId: string;
status: azdata.TaskStatus;
message?: string;
script?: string;
}
export class TaskService implements ITaskService {
public _serviceBrand: undefined;
private _taskQueue: TaskNode;
private _onTaskComplete = new Emitter<TaskNode>();
private _onAddNewTask = new Emitter<TaskNode>();
private _providers: { [handle: string]: azdata.TaskServicesProvider; } = Object.create(null);
constructor(
@ILifecycleService lifecycleService: ILifecycleService,
@IDialogService private dialogService: IDialogService,
@IQueryEditorService private queryEditorService: IQueryEditorService,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService
) {
this._taskQueue = new TaskNode('Root');
this._onTaskComplete = new Emitter<TaskNode>();
this._onAddNewTask = new Emitter<TaskNode>();
lifecycleService.onBeforeShutdown(event => event.veto(this.beforeShutdown()));
}
/**
* Register a ObjectExplorer provider
*/
public registerProvider(providerId: string, provider: azdata.TaskServicesProvider): void {
this._providers[providerId] = provider;
}
public onNewTaskCreated(handle: number, taskInfo: azdata.TaskInfo) {
this.createNewTask(taskInfo);
}
public createNewTask(taskInfo: azdata.TaskInfo) {
let databaseName: string = taskInfo.databaseName;
let serverName: string = taskInfo.serverName;
if (taskInfo && taskInfo.connection) {
let connectionProfile = this.connectionManagementService.getConnectionProfile(taskInfo.connection.connectionId);
if (connectionProfile && !!databaseName) {
databaseName = connectionProfile.databaseName;
}
if (connectionProfile && !!serverName) {
serverName = connectionProfile.serverName;
}
}
let node: TaskNode = new TaskNode(taskInfo.name, serverName, databaseName, taskInfo.taskId, taskInfo.taskExecutionMode, taskInfo.isCancelable);
node.providerName = taskInfo.providerName;
this.handleNewTask(node);
}
public updateTask(taskProgressInfo: azdata.TaskProgressInfo) {
this.handleTaskComplete({
taskId: taskProgressInfo.taskId,
status: taskProgressInfo.status,
message: taskProgressInfo.message,
script: taskProgressInfo.script
});
}
public onTaskStatusChanged(handle: number, taskProgressInfo: azdata.TaskProgressInfo) {
this.updateTask(taskProgressInfo);
}
public cancelTask(providerId: string, taskId: string): Promise<boolean | undefined> {
let task = this.getTaskInQueue(taskId);
if (task) {
task.status = TaskStatus.Canceling;
this._onTaskComplete.fire(task);
if (providerId) {
let provider = this._providers[providerId];
if (provider && provider.cancelTask) {
return Promise.resolve(provider.cancelTask({
taskId: taskId
}));
}
} else {
return Promise.resolve(true);
}
}
return Promise.resolve(undefined);
}
private cancelAllTasks(): Thenable<void> {
return new Promise<void>((resolve, reject) => {
let promises = this._taskQueue.children!.map(task => {
if (task.status === TaskStatus.InProgress || task.status === TaskStatus.NotStarted) {
return this.cancelTask(task.providerName!, task.id);
}
return Promise.resolve(true);
});
Promise.all(promises).then(result => {
resolve(undefined);
}).catch(error => {
reject(error);
});
});
}
public handleNewTask(task: TaskNode): void {
if (this._taskQueue.hasChildren) {
this._taskQueue.children!.unshift(task);
} else {
this._taskQueue.hasChildren = true;
this._taskQueue.children = [task];
}
this._onAddNewTask.fire(task);
}
public beforeShutdown(): Promise<boolean> {
const message = localize('InProgressWarning', "1 or more tasks are in progress. Are you sure you want to quit?");
const options = [
localize('taskService.yes', "Yes"),
localize('taskService.no', "No")
];
return new Promise<boolean>((resolve, reject) => {
let numOfInprogressTasks = this.getNumberOfInProgressTasks();
if (numOfInprogressTasks > 0) {
this.dialogService.show(Severity.Warning, message, options).then(choice => {
switch (choice.choice) {
case 0:
let timeout: any;
let isTimeout = false;
this.cancelAllTasks().then(() => {
clearTimeout(timeout);
if (!isTimeout) {
resolve(false);
}
}, error => {
clearTimeout(timeout);
if (!isTimeout) {
resolve(false);
}
});
timeout = setTimeout(function () {
isTimeout = true;
resolve(false);
}, 2000);
break;
case 1:
resolve(true);
}
});
} else {
resolve(false);
}
});
}
public handleTaskComplete(eventArgs: TaskStatusChangeArgs): void {
let task = this.getTaskInQueue(eventArgs.taskId);
if (task) {
task.status = eventArgs.status;
if (eventArgs.message) {
task.message = eventArgs.message;
}
switch (task.status) {
case TaskStatus.Canceled:
case TaskStatus.Succeeded:
case TaskStatus.SucceededWithWarning:
case TaskStatus.Failed:
task.endTime = new Date().toLocaleTimeString();
task.timer.stop();
this._onTaskComplete.fire(task);
break;
default:
break;
}
if ((task.status === TaskStatus.Succeeded || task.status === TaskStatus.SucceededWithWarning)
&& eventArgs.script && eventArgs.script !== '') {
if (task.taskExecutionMode === TaskExecutionMode.script) {
this.queryEditorService.newSqlEditor(eventArgs.script);
} else if (task.taskExecutionMode === TaskExecutionMode.executeAndScript) {
task.script = eventArgs.script;
}
}
}
}
private getTaskInQueue(taskId: string): TaskNode | undefined {
if (this._taskQueue.hasChildren) {
return find(this._taskQueue.children!, x => x.id === taskId);
}
return undefined;
}
public get onTaskComplete(): Event<TaskNode> {
return this._onTaskComplete.event;
}
public get onAddNewTask(): Event<TaskNode> {
return this._onAddNewTask.event;
}
public getNumberOfInProgressTasks(): number {
if (this._taskQueue.hasChildren) {
let inProgressTasks = this._taskQueue.children!.filter(x => x.status === TaskStatus.InProgress);
return inProgressTasks ? inProgressTasks.length : 0;
}
return 0;
}
public getAllTasks(): TaskNode {
return this._taskQueue;
}
}

View File

@@ -9,23 +9,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import * as cr from 'vs/platform/theme/common/colorRegistry';
import { attachStyler } from 'vs/platform/theme/common/styler';
import { IDisposable } from 'vs/base/common/lifecycle';
import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme';
import { IThemable } from 'vs/base/common/styler';
export function attachModalDialogStyler(widget: IThemable, themeService: IThemeService, style?:
{
dialogForeground?: cr.ColorIdentifier,
dialogHeaderAndFooterBackground?: cr.ColorIdentifier,
dialogBodyBackground?: cr.ColorIdentifier,
}): IDisposable {
return attachStyler(themeService, {
dialogForeground: (style && style.dialogForeground) || cr.foreground,
dialogBorder: cr.contrastBorder,
dialogHeaderAndFooterBackground: (style && style.dialogHeaderAndFooterBackground) || SIDE_BAR_BACKGROUND,
dialogBodyBackground: (style && style.dialogBodyBackground) || cr.editorBackground
}, widget);
}
export function attachDropdownStyler(widget: IThemable, themeService: IThemeService, style?:
{
backgroundColor?: cr.ColorIdentifier,
@@ -270,22 +255,3 @@ export function attachCheckboxStyler(widget: IThemable, themeService: IThemeServ
disabledCheckboxForeground: (style && style.disabledCheckboxForeground) || sqlcolors.disabledCheckboxForeground
}, widget);
}
export function attachPanelStyler(widget: IThemable, themeService: IThemeService) {
return attachStyler(themeService, {
headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND,
headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND,
// headerHighContrastBorder: index === 0 ? null : contrastBorder,
dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND
}, widget);
}
export function attachTabbedPanelStyler(widget: IThemable, themeService: IThemeService) {
return attachStyler(themeService, {
titleActiveForeground: PANEL_ACTIVE_TITLE_FOREGROUND,
titleActiveBorder: PANEL_ACTIVE_TITLE_BORDER,
titleInactiveForeground: PANEL_INACTIVE_TITLE_FOREGROUND,
focusBorder: cr.focusBorder,
outline: cr.activeContrastBorder
}, widget);
}