mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-23 17:23:02 -05:00
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:
@@ -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
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
129
src/sql/platform/dashboard/browser/interfaces.ts
Normal file
129
src/sql/platform/dashboard/browser/interfaces.ts
Normal 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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 '';
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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());
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user