SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,125 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import nls = require('vs/nls');
import 'vs/css!sql/media/actionBarLabel';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { localize } from 'vs/nls';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ToggleViewletAction } from 'vs/workbench/browser/viewlet';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actionRegistry';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { VIEWLET_ID } from 'sql/parts/taskHistory/viewlet/taskHistoryViewlet';
import lifecycle = require('vs/base/common/lifecycle');
import ext = require('vs/workbench/common/contributions');
import { ITaskService } from 'sql/parts/taskHistory/common/taskService';
import { IActivityBarService, NumberBadge } from 'vs/workbench/services/activity/common/activityBarService';
export class StatusUpdater implements ext.IWorkbenchContribution {
static ID = 'data.taskhistory.statusUpdater';
private badgeHandle: lifecycle.IDisposable;
private toDispose: lifecycle.IDisposable[];
constructor(
@IActivityBarService private activityBarService: IActivityBarService,
@ITaskService private _taskService: ITaskService,
@IViewletService private _viewletService: IViewletService
) {
this.toDispose = [];
this.toDispose.push(this._taskService.onAddNewTask(args => {
this.showTasksViewlet();
this.onServiceChange();
}));
this.toDispose.push(this._taskService.onTaskComplete(task => {
this.onServiceChange();
}));
}
private showTasksViewlet(): void {
let activeViewlet: IViewlet = this._viewletService.getActiveViewlet();
if (!activeViewlet || activeViewlet.getId() !== VIEWLET_ID) {
this._viewletService.openViewlet(VIEWLET_ID, true);
}
}
private onServiceChange(): void {
lifecycle.dispose(this.badgeHandle);
let numOfInProgressTask: number = this._taskService.getNumberOfInProgressTasks();
let badge: NumberBadge = new NumberBadge(numOfInProgressTask, n => localize('inProgressTasksChangesBadge', "{0} in progress tasks", n));
this.badgeHandle = this.activityBarService.showActivity(VIEWLET_ID, badge, 'taskhistory-viewlet-label');
}
public getId(): string {
return StatusUpdater.ID;
}
public dispose(): void {
this.toDispose = lifecycle.dispose(this.toDispose);
lifecycle.dispose(this.badgeHandle);
}
}
// Viewlet Action
export class TaskHistoryViewletAction extends ToggleViewletAction {
public static ID = VIEWLET_ID;
public static LABEL = nls.localize({ key: 'showTaskHistory', comment: ['Show Task History'] }, 'Show Task History');
constructor(
id: string,
label: string,
@IViewletService viewletService: IViewletService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService
) {
super(id, label, VIEWLET_ID, viewletService, editorService);
}
}
// Viewlet
const viewletDescriptor = new ViewletDescriptor(
'sql/parts/taskHistory/viewlet/taskHistoryViewlet',
'TaskHistoryViewlet',
VIEWLET_ID,
'Task History',
'taskHistoryViewlet',
-90
);
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor);
// Register StatusUpdater
(<ext.IWorkbenchContributionsRegistry>Registry.as(ext.Extensions.Workbench)).registerWorkbenchContribution(StatusUpdater);
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(
new SyncActionDescriptor(
TaskHistoryViewletAction,
TaskHistoryViewletAction.ID,
TaskHistoryViewletAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyCode.KEY_T }),
'View: Show Task Histry',
localize('view', "View")
);
let configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'taskHistory',
'title': localize('taskHistory', 'Task History'),
'type': 'object',
'properties': {
'datasource.task': {
'description': localize('datasource.task', 'Operation Task Status'),
'type': 'array'
}
}
});

View File

@@ -0,0 +1,115 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
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, taskExecutionMode: TaskExecutionMode = TaskExecutionMode.execute, isCancelable: boolean = true) {
this.id = taskId || generateUuid();
this.taskName = taskName;
this.serverName = serverName;
this.databaseName = databaseName;
this.timer = StopWatch.create();
this.startTime = new Date().toLocaleTimeString();
this.status = TaskStatus.inProgress;
this.hasChildren = false;
this.taskExecutionMode = taskExecutionMode;
this.isCancelable = isCancelable;
}
}

View File

@@ -0,0 +1,226 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as data from 'data';
import { TaskNode, TaskStatus, TaskExecutionMode } from 'sql/parts/taskHistory/common/taskNode';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import Event, { Emitter } from 'vs/base/common/event';
export const SERVICE_ID = 'taskHistoryService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IChoiceService } from 'vs/platform/message/common/message';
import { localize } from 'vs/nls';
import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
export const ITaskService = createDecorator<ITaskService>(SERVICE_ID);
export interface ITaskService {
_serviceBrand: any;
onTaskComplete: Event<TaskNode>;
onAddNewTask: Event<TaskNode>;
handleNewTask(task: TaskNode): void;
handleTaskComplete(eventArgs: TaskStatusChangeArgs): void;
getAllTasks(): TaskNode;
getNumberOfInProgressTasks(): number;
onNewTaskCreated(handle: number, taskInfo: data.TaskInfo);
onTaskStatusChanged(handle: number, taskProgressInfo: data.TaskProgressInfo);
cancelTask(providerId: string, taskId: string): Thenable<boolean>;
/**
* Register a ObjectExplorer provider
*/
registerProvider(providerId: string, provider: data.TaskServicesProvider): void;
}
export interface TaskStatusChangeArgs {
taskId: string;
status: data.TaskStatus;
message?: string;
script?: string;
}
export class TaskService implements ITaskService {
public _serviceBrand: any;
private _taskQueue: TaskNode;
private _onTaskComplete = new Emitter<TaskNode>();
private _onAddNewTask = new Emitter<TaskNode>();
private _providers: { [handle: string]: data.TaskServicesProvider; } = Object.create(null);
constructor(
@ILifecycleService lifecycleService: ILifecycleService,
@IChoiceService private choiceService: IChoiceService,
@IQueryEditorService private queryEditorService: IQueryEditorService
) {
this._taskQueue = new TaskNode('Root', undefined, undefined);
this._onTaskComplete = new Emitter<TaskNode>();
this._onAddNewTask = new Emitter<TaskNode>();
lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown()));
}
/**
* Register a ObjectExplorer provider
*/
public registerProvider(providerId: string, provider: data.TaskServicesProvider): void {
this._providers[providerId] = provider;
}
public onNewTaskCreated(handle: number, taskInfo: data.TaskInfo) {
let node: TaskNode = new TaskNode(taskInfo.name, taskInfo.serverName, taskInfo.databaseName, taskInfo.taskId, taskInfo.taskExecutionMode, taskInfo.isCancelable);
node.providerName = taskInfo.providerName;
this.handleNewTask(node);
}
public onTaskStatusChanged(handle: number, taskProgressInfo: data.TaskProgressInfo) {
this.handleTaskComplete({
taskId: taskProgressInfo.taskId,
status: taskProgressInfo.status,
message: taskProgressInfo.message,
script: taskProgressInfo.script
});
}
public cancelTask(providerId: string, taskId: string): Thenable<boolean> {
let task = this.getTaskInQueue(taskId);
task.status = TaskStatus.canceling;
this._onTaskComplete.fire(task);
let provider = this._providers[providerId];
if (provider) {
return provider.cancelTask({
taskId: taskId
});
}
return Promise.resolve(undefined);
}
private cancelAllTasks(): Thenable<void> {
return new TPromise<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(): TPromise<boolean> {
const message = localize('InProgressWarning', '1 or more tasks are in progress. Are you sure you want to quit?');
const options = [
localize('yes', "Yes"),
localize('no', "No")
];
return new TPromise<boolean>((resolve, reject) => {
let numOfInprogressTasks = this.getNumberOfInProgressTasks();
if (numOfInprogressTasks > 0) {
this.choiceService.choose(Severity.Warning, message, options, 0, false).done(choice => {
switch (choice) {
case 0:
let timeoutId: number;
let isTimeout = false;
this.cancelAllTasks().then(() => {
clearTimeout(timeoutId);
if (!isTimeout) {
resolve(false);
}
}, error => {
clearTimeout(timeoutId);
if (!isTimeout) {
resolve(false);
};
});
timeoutId = setTimeout(function () {
isTimeout = true;
resolve(false);
}, 2000);
break;
case 1:
resolve(true);
}
});
} else {
resolve(false);
}
});
}
public handleTaskComplete(eventArgs: TaskStatusChangeArgs): void {
var 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 {
if (this._taskQueue.hasChildren) {
return this._taskQueue.children.find(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) {
var inProgressTasks = this._taskQueue.children.filter(x => x.status === TaskStatus.inProgress);
return inProgressTasks ? inProgressTasks.length : 0;
}
return 0;
}
public getAllTasks(): TaskNode {
return this._taskQueue;
}
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>queuedtask_16x16</title><path class="cls-1" d="M4,7a3.45,3.45,0,0,1-.93-.13,3.56,3.56,0,0,1-.84-.35A3.5,3.5,0,0,1,1,5.28,3.54,3.54,0,0,1,.6,4.44a3.54,3.54,0,0,1,0-1.87A3.54,3.54,0,0,1,1,1.74,3.5,3.5,0,0,1,2.21.48,3.55,3.55,0,0,1,3.05.13a3.54,3.54,0,0,1,1.87,0,3.55,3.55,0,0,1,.84.35A3.5,3.5,0,0,1,7,1.74a3.55,3.55,0,0,1,.35.84,3.54,3.54,0,0,1,0,1.87A3.56,3.56,0,0,1,7,5.28,3.5,3.5,0,0,1,5.76,6.54a3.55,3.55,0,0,1-.84.35A3.45,3.45,0,0,1,4,7ZM4,.44a3,3,0,0,0-.81.11A3.08,3.08,0,0,0,1,2.7,3.08,3.08,0,0,0,1,4.33,3.08,3.08,0,0,0,3.17,6.47a3.08,3.08,0,0,0,1.63,0A3.08,3.08,0,0,0,6.95,4.33a3.08,3.08,0,0,0,0-1.63A3.08,3.08,0,0,0,4.8.55,3,3,0,0,0,4,.44ZM4,3.51V1.32H3.55V3.95H5.3V3.51Z"/><rect class="cls-1" x="1" y="14.01" width="14" height="1"/><rect class="cls-1" x="1" y="11.03" width="14" height="1"/><rect class="cls-1" x="1" y="7.96" width="14" height="1"/><rect class="cls-1" x="9" y="4.88" width="5.97" height="1"/><rect class="cls-1" x="9" y="1.88" width="5.97" height="1"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>queuedtask_inverse_16x16</title><path class="cls-1" d="M4.08,7.37a3.45,3.45,0,0,1-.93-.13,3.56,3.56,0,0,1-.84-.35A3.5,3.5,0,0,1,1,5.63,3.54,3.54,0,0,1,.7,4.79a3.54,3.54,0,0,1,0-1.87A3.54,3.54,0,0,1,1,2.09,3.5,3.5,0,0,1,2.31.83,3.55,3.55,0,0,1,3.15.47,3.54,3.54,0,0,1,5,.47a3.55,3.55,0,0,1,.84.35A3.5,3.5,0,0,1,7.11,2.09a3.55,3.55,0,0,1,.35.84,3.54,3.54,0,0,1,0,1.87,3.56,3.56,0,0,1-.35.84A3.5,3.5,0,0,1,5.85,6.89,3.55,3.55,0,0,1,5,7.25,3.45,3.45,0,0,1,4.08,7.37Zm0-6.58A3,3,0,0,0,3.27.9,3.08,3.08,0,0,0,1.12,3a3.08,3.08,0,0,0,0,1.63A3.08,3.08,0,0,0,3.27,6.82a3.08,3.08,0,0,0,1.63,0A3.08,3.08,0,0,0,7,4.67,3.08,3.08,0,0,0,7,3,3.08,3.08,0,0,0,4.9.9,3,3,0,0,0,4.08.79Zm0,3.07V1.67H3.64V4.3H5.4V3.86Z"/><rect class="cls-1" x="1.09" y="14.36" width="14" height="1"/><rect class="cls-1" x="1.09" y="11.38" width="14" height="1"/><rect class="cls-1" x="1.09" y="8.31" width="14" height="1"/><rect class="cls-1" x="9.09" y="5.22" width="5.97" height="1"/><rect class="cls-1" x="9.09" y="2.22" width="5.97" height="1"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.empty-task-message {
padding: 10px 22px 0 22px;
opacity: 0.5;
}
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .task-group {
padding: 5px;
overflow: hidden;
}
/* task title and description */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .task-group > .task-details > .title {
text-overflow: ellipsis;
overflow: hidden;
font-weight: 700;
}
/* style for server name | database name */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .task-group > .task-details > .description {
text-overflow: ellipsis;
overflow: hidden;
padding-left: 25px
}
/* style for timing */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .task-group > .task-details > .time {
padding-left: 25px;
opacity: .6;
font-size: 80%;
text-overflow: ellipsis;
overflow: hidden;
}
/* task icon status */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .task-group > .task-icon {
float: left;
height: 16px;
width: 16px;
padding-right: 10px;
}
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .task-group > .task-icon.not-started {
content: url('status_queuedtask.svg');
}
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .task-group > .task-icon.not-started,
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .task-group > .task-icon.not-started {
content: url('status_queuedtask_inverse.svg');
}

View File

@@ -0,0 +1,69 @@
/*---------------------------------------------------------------------------------------------
* 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 { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import { ITaskService } from 'sql/parts/taskHistory/common/taskService';
import { TaskNode } from 'sql/parts/taskHistory/common/taskNode';
import { IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import Severity from 'vs/base/common/severity';
export class CancelAction extends Action {
public static ID = 'taskHistory.cancel';
public static LABEL = localize('cancel', 'Cancel');
constructor(
id: string,
label: string,
@ITaskService private _taskService: ITaskService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) {
super(id, label);
}
public run(element: TaskNode): TPromise<boolean> {
if (element instanceof TaskNode) {
this._taskService.cancelTask(element.providerName, element.id).then((result) => {
if (!result) {
let error = localize('errorMsgFromCancelTask', 'The task is failed to cancel.');
this.showError(error);
}
}, error => {
this.showError(error);
return TPromise.as(true);
});
}
return TPromise.as(true);
}
private showError(errorMessage: string) {
if (this._errorMessageService) {
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
}
}
}
export class ScriptAction extends Action {
public static ID = 'taskHistory.script';
public static LABEL = localize('script', 'Script');
constructor(
id: string,
label: string,
@IQueryEditorService private _queryEditorService: IQueryEditorService
) {
super(id, label);
}
public run(element: TaskNode): TPromise<boolean> {
if (element instanceof TaskNode) {
if (element.script && element.script !== '') {
this._queryEditorService.newSqlEditor(element.script);
}
}
return TPromise.as(true);
}
}

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { ContributableActionProvider } from 'vs/workbench/browser/actions';
import { IAction } from 'vs/base/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TaskNode, TaskStatus, TaskExecutionMode } from 'sql/parts/taskHistory/common/taskNode';
import { CancelAction, ScriptAction } from 'sql/parts/taskHistory/viewlet/taskAction';
/**
* Provides actions for the history tasks
*/
export class TaskHistoryActionProvider extends ContributableActionProvider {
constructor(
@IInstantiationService private _instantiationService: IInstantiationService
) {
super();
}
public hasActions(tree: ITree, element: any): boolean {
return element instanceof TaskNode;
}
/**
* Return actions given an element in the tree
*/
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
if (element instanceof TaskNode) {
return TPromise.as(this.getTaskHistoryActions(tree, element));
}
return TPromise.as([]);
}
public hasSecondaryActions(tree: ITree, element: any): boolean {
return false;
}
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
return super.getSecondaryActions(tree, element);
}
/**
* Return actions for history task
*/
public getTaskHistoryActions(tree: ITree, element: TaskNode): IAction[] {
var actions = [];
// get actions for tasks in progress
if (element.status === TaskStatus.inProgress && element.isCancelable) {
actions.push(this._instantiationService.createInstance(CancelAction, CancelAction.ID, CancelAction.LABEL));
}
// get actions for tasks succeeded
if (element.status === TaskStatus.succeeded || element.status === TaskStatus.succeededWithWarning) {
if (element.taskExecutionMode === TaskExecutionMode.executeAndScript) {
actions.push(this._instantiationService.createInstance(ScriptAction, ScriptAction.ID, ScriptAction.LABEL));
}
}
return actions;
}
}

View File

@@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ITree, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
import treedefaults = require('vs/base/parts/tree/browser/treeDefaults');
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { TaskHistoryActionProvider } from 'sql/parts/taskHistory/viewlet/taskHistoryActionProvider';
/**
* Extends the tree controller to handle clicks on the tree elements
*/
export class TaskHistoryController extends treedefaults.DefaultController {
constructor(private actionProvider: TaskHistoryActionProvider,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IContextMenuService private contextMenuService: IContextMenuService,
@ITelemetryService private telemetryService: ITelemetryService,
@IKeybindingService private keybindingService: IKeybindingService
) {
super({ clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN });
}
public onClick(tree: ITree, element: any, event: IMouseEvent): boolean {
return super.onClick(tree, element, event);
}
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent, origin: string = 'mouse'): boolean {
return super.onLeftClick(tree, element, event, origin);
}
// Do not allow left / right to expand and collapse groups #7848
protected onLeft(tree: ITree, event: IKeyboardEvent): boolean {
return true;
}
protected onRight(tree: ITree, event: IKeyboardEvent): boolean {
return true;
}
protected onEnter(tree: ITree, event: IKeyboardEvent): boolean {
return super.onEnter(tree, event);
}
/**
* Return actions in the context menu
*/
public onContextMenu(tree: ITree, element: any, event: ContextMenuEvent): boolean {
if (event.target && event.target.tagName && event.target.tagName.toLowerCase() === 'input') {
return false;
}
// Check if clicked on some element
if (element === tree.getInput()) {
return false;
}
event.preventDefault();
event.stopPropagation();
tree.setFocus(element);
let anchor = { x: event.posx + 1, y: event.posy };
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => this.actionProvider.getActions(tree, element),
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id),
onHide: (wasCancelled?: boolean) => {
if (wasCancelled) {
tree.DOMFocus();
}
},
getActionsContext: () => (element)
});
return true;
}
}

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
import { TaskNode } from 'sql/parts/taskHistory/common/taskNode';
import { TPromise } from 'vs/base/common/winjs.base';
/**
* Implements the DataSource(that returns a parent/children of an element) for the task history
*/
export class TaskHistoryDataSource implements IDataSource {
/**
* Returns the unique identifier of the given element.
* No more than one element may use a given identifier.
*/
public getId(tree: ITree, element: any): string {
if (element instanceof TaskNode) {
return (<TaskNode>element).id;
} else {
return undefined;
}
}
/**
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(tree: ITree, element: any): boolean {
if (element instanceof TaskNode) {
return (<TaskNode>element).hasChildren;
}
return false;
}
/**
* Returns the element's children as an array in a promise.
*/
public getChildren(tree: ITree, element: any): TPromise<any> {
if (element instanceof TaskNode) {
return TPromise.as((<TaskNode>element).children);
}
return TPromise.as(null);
}
/**
* Returns the element's parent in a promise.
*/
public getParent(tree: ITree, element: any): TPromise<any> {
return TPromise.as(null);
}
}

View File

@@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { $ } from 'vs/base/browser/dom';
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { ITaskHistoryTemplateData } from 'sql/parts/taskHistory/viewlet/templateData';
import { TaskNode, TaskStatus } from 'sql/parts/taskHistory/common/taskNode';
import dom = require('vs/base/browser/dom');
import { localize } from 'vs/nls';
import * as Utils from 'sql/parts/connection/common/utils';
/**
* Renders the tree items.
* Uses the dom template to render task history.
*/
export class TaskHistoryRenderer implements IRenderer {
public static readonly TASKOBJECT_HEIGHT = 65;
private static readonly ICON_CLASS = 'task-icon icon';
private static readonly TASKOBJECT_TEMPLATE_ID = 'carbonTask';
private static readonly FAIL_CLASS = 'error';
private static readonly SUCCESS_CLASS = 'success';
private static readonly INPROGRESS_CLASS = 'in-progress';
private static readonly NOTSTARTED_CLASS = 'not-started';
private static readonly CANCELED_CLASS = 'canceled';
/**
* Returns the element's height in the tree, in pixels.
*/
public getHeight(tree: ITree, element: any): number {
return TaskHistoryRenderer.TASKOBJECT_HEIGHT;
}
/**
* Returns a template ID for a given element.
*/
public getTemplateId(tree: ITree, element: any): string {
return TaskHistoryRenderer.TASKOBJECT_TEMPLATE_ID;
}
/**
* Render template in a dom element based on template id
*/
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
const taskTemplate: ITaskHistoryTemplateData = Object.create(null);
taskTemplate.root = dom.append(container, $('.task-group'));
taskTemplate.icon = dom.append(taskTemplate.root, $('img.task-icon'));
var titleContainer = dom.append(taskTemplate.root, $('div.task-details'));
taskTemplate.title = dom.append(titleContainer, $('div.title'));
taskTemplate.description = dom.append(titleContainer, $('div.description'));
taskTemplate.time = dom.append(titleContainer, $('div.time'));
return taskTemplate;
}
/**
* Render a element, given an object bag returned by the template
*/
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
this.renderTask(tree, element, templateData);
}
private renderTask(tree: ITree, taskNode: TaskNode, templateData: ITaskHistoryTemplateData): void {
let taskStatus;
if (taskNode) {
templateData.icon.className = TaskHistoryRenderer.ICON_CLASS;
switch (taskNode.status) {
case TaskStatus.succeeded:
templateData.icon.classList.add(TaskHistoryRenderer.SUCCESS_CLASS);
taskStatus = localize('succeeded', "succeeded");
break;
case TaskStatus.failed:
templateData.icon.classList.add(TaskHistoryRenderer.FAIL_CLASS);
taskStatus = localize('failed', "failed");
break;
case TaskStatus.inProgress:
templateData.icon.classList.add(TaskHistoryRenderer.INPROGRESS_CLASS);
taskStatus = localize('inProgress', "in progress");
break;
case TaskStatus.notStarted:
templateData.icon.classList.add(TaskHistoryRenderer.NOTSTARTED_CLASS);
taskStatus = localize('notStarted', "not started");
break;
case TaskStatus.canceled:
templateData.icon.classList.add(TaskHistoryRenderer.CANCELED_CLASS);
taskStatus = localize('canceled', "canceled");
break;
case TaskStatus.canceling:
templateData.icon.classList.add(TaskHistoryRenderer.INPROGRESS_CLASS);
taskStatus = localize('canceling', "canceling");
break;
}
// Set hover text for icon to same as task status
templateData.icon.title = taskStatus;
// Determine the task title and set hover text equal to that
templateData.title.textContent = taskNode.taskName + ' ' + taskStatus;
templateData.title.title = templateData.title.textContent;
// Determine the target name and set hover text equal to that
let description = taskNode.serverName;
if (taskNode.databaseName) {
description += ' | ' + taskNode.databaseName;
}
templateData.description.textContent = description;
templateData.description.title = templateData.description.textContent;
this.timer(taskNode, templateData);
let self = this;
setInterval(function () {
self.timer(taskNode, templateData);
}, 1000);
}
}
public timer(taskNode: TaskNode, templateData: ITaskHistoryTemplateData) {
let timeLabel = '';
if (taskNode.status === TaskStatus.failed) {
timeLabel += taskNode.startTime + ' Error: ' + taskNode.message;
} else {
if (taskNode.startTime) {
timeLabel = taskNode.startTime;
}
if (taskNode.endTime) {
timeLabel += ' - ' + taskNode.endTime;
}
if (taskNode.timer) {
// Round task duration to seconds and then convert back to milliseconds
let duration = Math.floor(taskNode.timer.elapsed() / 1000) * 1000;
timeLabel += ' (' + Utils.parseNumAsTimeString(duration) + ')';
}
}
templateData.time.textContent = timeLabel;
templateData.time.title = timeLabel;
}
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
// no op
// InputBox disposed in wrapUp
}
}

View File

@@ -0,0 +1,177 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import nls = require('vs/nls');
import errors = require('vs/base/common/errors');
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import Severity from 'vs/base/common/severity';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import * as builder from 'vs/base/browser/builder';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { DefaultFilter, DefaultDragAndDrop, DefaultAccessibilityProvider } from 'vs/base/parts/tree/browser/treeDefaults';
import { localize } from 'vs/nls';
import { TaskHistoryRenderer } from 'sql/parts/taskHistory/viewlet/taskHistoryRenderer';
import { TaskHistoryDataSource } from 'sql/parts/taskHistory/viewlet/taskHistoryDataSource';
import { TaskHistoryController } from 'sql/parts/taskHistory/viewlet/taskHistoryController';
import { TaskHistoryActionProvider } from 'sql/parts/taskHistory/viewlet/taskHistoryActionProvider';
import { ITaskService } from 'sql/parts/taskHistory/common/taskService';
import { TaskNode, TaskStatus } from 'sql/parts/taskHistory/common/taskNode';
import { IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
const $ = builder.$;
/**
* TaskHistoryView implements the dynamic tree view.
*/
export class TaskHistoryView {
private _messages: builder.Builder;
private _tree: ITree;
private _toDispose: IDisposable[] = [];
constructor(
@IInstantiationService private _instantiationService: IInstantiationService,
@ITaskService private _taskService: ITaskService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IThemeService private _themeService: IThemeService
) {
}
/**
* Render the view body
*/
public renderBody(container: HTMLElement): void {
let taskNode = this._taskService.getAllTasks();
// Add div to display no task executed message
this._messages = $('div.empty-task-message').appendTo(container);
if (taskNode && taskNode.hasChildren) {
this._messages.hide();
}
let noTaskMessage = localize('noTaskMessage', 'No task history to display. Try backup or restore task to view its execution status.');
$('span').text(noTaskMessage).appendTo(this._messages);
this._tree = this.createTaskHistoryTree(container, this._instantiationService);
this._toDispose.push(this._tree.addListener('selection', (event) => this.onSelected(event)));
// Theme styler
this._toDispose.push(attachListStyler(this._tree, this._themeService));
this._toDispose.push(this._taskService.onAddNewTask(args => {
this._messages.hide();
this.refreshTree();
}));
this._toDispose.push(this._taskService.onTaskComplete(task => {
this.updateTask(task);
}));
// Refresh Tree when these events are emitted
this.refreshTree();
}
/**
* Create a task history tree
*/
public createTaskHistoryTree(treeContainer: HTMLElement, instantiationService: IInstantiationService): Tree {
const dataSource = instantiationService.createInstance(TaskHistoryDataSource);
const actionProvider = instantiationService.createInstance(TaskHistoryActionProvider);
const renderer = instantiationService.createInstance(TaskHistoryRenderer);
const controller = instantiationService.createInstance(TaskHistoryController, actionProvider);
const dnd = new DefaultDragAndDrop();
const filter = new DefaultFilter();
const sorter = null;
const accessibilityProvider = new DefaultAccessibilityProvider();
return new Tree(treeContainer, {
dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider
}, {
indentPixels: 10,
twistiePixels: 20,
ariaLabel: nls.localize({ key: 'regTreeAriaLabel', comment: ['TaskHistory'] }, 'Task history')
});
}
private updateTask(task: TaskNode): void {
this._tree.refresh(task);
}
public refreshTree(): void {
let selectedElement: any;
let targetsToExpand: any[];
// Focus
this._tree.DOMFocus();
if (this._tree) {
let selection = this._tree.getSelection();
if (selection && selection.length === 1) {
selectedElement = <any>selection[0];
}
targetsToExpand = this._tree.getExpandedElements();
}
//Get the tree Input
let treeInput = this._taskService.getAllTasks();
if (treeInput) {
this._tree.setInput(treeInput).then(() => {
// Make sure to expand all folders that where expanded in the previous session
if (targetsToExpand) {
this._tree.expandAll(targetsToExpand);
}
if (selectedElement) {
this._tree.select(selectedElement);
}
this._tree.getFocus();
}, errors.onUnexpectedError);
}
}
private onSelected(event: any) {
let selection = this._tree.getSelection();
if (selection && selection.length > 0 && (selection[0] instanceof TaskNode)) {
let task = <TaskNode>selection[0];
let isMouseOrigin = event.payload && (event.payload.origin === 'mouse');
let isDoubleClick = isMouseOrigin && event.payload.originalEvent && event.payload.originalEvent.detail === 2;
if (isDoubleClick) {
if (task.status === TaskStatus.failed) {
var err = task.taskName + ': ' + task.message;
this._errorMessageService.showDialog(Severity.Error, nls.localize('taskError','Task Error'), err);
}
}
}
}
/**
* set the layout of the view
*/
public layout(height: number): void {
this._tree.layout(height);
}
/**
* set the visibility of the view
*/
public setVisible(visible: boolean): void {
if (visible) {
this._tree.onVisible();
} else {
this._tree.onHidden();
}
}
/**
* dispose the server tree view
*/
public dispose(): void {
this._tree.dispose();
this._toDispose = dispose(this._toDispose);
}
}

View File

@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!sql/media/icons/common-icons';
import 'vs/css!./media/taskHistoryViewlet';
import { TPromise } from 'vs/base/common/winjs.base';
import { Builder, Dimension } from 'vs/base/browser/builder';
import { Viewlet } from 'vs/workbench/browser/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { toggleClass } from 'vs/base/browser/dom';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IMessageService } from 'vs/platform/message/common/message';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import Severity from 'vs/base/common/severity';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { TaskHistoryView } from 'sql/parts/taskHistory/viewlet/taskHistoryView';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
export const VIEWLET_ID = 'workbench.view.taskHistory';
export class TaskHistoryViewlet extends Viewlet {
private _root: HTMLElement;
private _toDisposeViewlet: IDisposable[] = [];
private _taskHistoryView: TaskHistoryView;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IViewletService private viewletService: IViewletService,
@IMessageService private messageService: IMessageService
) {
super(VIEWLET_ID, telemetryService, themeService);
}
private onError(err: any): void {
if (isPromiseCanceledError(err)) {
return;
}
this.messageService.show(Severity.Error, err);
}
public create(parent: Builder): TPromise<void> {
super.create(parent);
this._root = parent.getHTMLElement();
this._taskHistoryView = this._instantiationService.createInstance(TaskHistoryView);
this._taskHistoryView.renderBody(parent.getHTMLElement());
return TPromise.as(null);
}
public setVisible(visible: boolean): TPromise<void> {
return super.setVisible(visible).then(() => {
this._taskHistoryView.setVisible(visible);
});
}
public focus(): void {
super.focus();
}
public layout({ height, width }: Dimension): void {
this._taskHistoryView.layout(height);
toggleClass(this._root, 'narrow', width <= 350);
}
public getOptimalWidth(): number {
return 400;
}
public dispose(): void {
this._toDisposeViewlet = dispose(this._toDisposeViewlet);
}
}

View File

@@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface ITaskHistoryTemplateData {
root: HTMLElement;
icon: HTMLElement;
title: HTMLSpanElement;
description: HTMLSpanElement;
time: HTMLSpanElement;
}