Layer Object Explorer; query plan; task history (#5030)
* relayer query plan, task history, object-explorer * formatting
@@ -15,7 +15,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { TreeItemCollapsibleState } from 'sql/parts/objectExplorer/common/treeNode';
|
||||
import { TreeItemCollapsibleState } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
|
||||
@extHostNamedCustomer(SqlMainContext.MainThreadObjectExplorer)
|
||||
export class MainThreadObjectExplorer implements MainThreadObjectExplorerShape {
|
||||
|
||||
@@ -43,7 +43,7 @@ import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { dirname } from 'vs/base/common/resources';
|
||||
|
||||
import { ITreeItem, ITreeView } from 'sql/workbench/common/views';
|
||||
import { IOEShimService } from 'sql/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
||||
import { IOEShimService } from 'sql/workbench/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { NodeContextKey } from 'sql/workbench/parts/dataExplorer/common/nodeContext';
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { AddServerGroupAction, AddServerAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
|
||||
import { AddServerGroupAction, AddServerAction } from 'sql/workbench/parts/objectExplorer/browser/connectionTreeAction';
|
||||
import { ClearRecentConnectionsAction, GetCurrentConnectionStringAction } from 'sql/workbench/parts/connection/common/connectionActions';
|
||||
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z"/></svg>
|
||||
|
After Width: | Height: | Size: 139 B |
@@ -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;}.cls-2{fill:#3bb44a;}</style></defs><title>connected_active_server_16x16</title><path class="cls-1" d="M1.29.07V16h9.59a3.31,3.31,0,0,1-1.94-1H2.29V11h6a3.31,3.31,0,0,1,.54-.8A1.81,1.81,0,0,1,9,10H2.29v-9h8.53v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.3,1.8V4.25H5.75V1.8Zm2,2H3.8V2.3H5.25Z"/><circle class="cls-2" cx="11.24" cy="12.52" r="3.47"/></svg>
|
||||
|
After Width: | Height: | Size: 502 B |
@@ -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;}.cls-2{fill:#3bb44a;}</style></defs><title>connected_active_server_inverse_16x16</title><path class="cls-1" d="M1.29.07V16h9.59a3.31,3.31,0,0,1-1.94-1H2.29V11h6a3.31,3.31,0,0,1,.54-.8A1.81,1.81,0,0,1,9,10H2.29v-9h8.53v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.3,1.8V4.25H5.75V1.8Zm2,2H3.8V2.3H5.25Z"/><circle class="cls-2" cx="11.24" cy="12.52" r="3.47"/></svg>
|
||||
|
After Width: | Height: | Size: 507 B |
@@ -0,0 +1,138 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* --- Registered servers tree viewlet --- */
|
||||
.server-explorer-viewlet .monaco-tree .monaco-tree-row .content .server-group {
|
||||
cursor: default;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Bold font style does not go well with CJK fonts */
|
||||
.server-explorer-viewlet:lang(zh-Hans) .monaco-tree .monaco-tree-row .server-group,
|
||||
.server-explorer-viewlet:lang(zh-Hant) .monaco-tree .monaco-tree-row .server-group,
|
||||
.server-explorer-viewlet:lang(ja) .monaco-tree .monaco-tree-row .server-group,
|
||||
.server-explorer-viewlet:lang(ko) .monaco-tree .monaco-tree-row .server-group { font-weight: normal; }
|
||||
|
||||
/* High Contrast Theming */
|
||||
.hc-black .monaco-workbench .server-explorer-viewlet .server-group {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.monaco-workbench > .activitybar .monaco-action-bar .action-label.serverTree {
|
||||
background-size: 22px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% !important;
|
||||
}
|
||||
|
||||
.server-explorer-viewlet .object-explorer-view {
|
||||
height: calc(100% - 36px);
|
||||
}
|
||||
|
||||
.server-explorer-viewlet .server-group {
|
||||
height: 38px;
|
||||
line-height: 38px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.server-explorer-viewlet .monaco-action-bar .action-label {
|
||||
margin-right: 0.3em;
|
||||
margin-left: 0.3em;
|
||||
line-height: 15px;
|
||||
width: 10px !important;
|
||||
height: 10px !important;
|
||||
}
|
||||
|
||||
/* Add space beneath the button */
|
||||
.new-connection .monaco-text-button {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* display action buttons on hover */
|
||||
.server-explorer-viewlet .monaco-tree .monaco-tree-row > .content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Added to display the tree in connection dialog */
|
||||
.server-explorer-viewlet {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.explorer-servers {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* search box */
|
||||
.server-explorer-viewlet .search-box {
|
||||
padding-bottom: 4px;
|
||||
margin: auto;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
/* OE and connection element group */
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile,
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group {
|
||||
padding: 5px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* OE and connection label */
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .label,
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .label {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* OE and connection icon */
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon,
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .icon {
|
||||
float: left;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected {
|
||||
background: url('connected_active_server.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected,
|
||||
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected{
|
||||
background: url('connected_active_server_inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected {
|
||||
background: url('disconnected_server.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected,
|
||||
.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected{
|
||||
background: url('disconnected_server_inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
/* loading for OE node */
|
||||
.server-explorer-viewlet .monaco-tree .monaco-tree-rows > .monaco-tree-row > .icon.in-progress .connection-tile:before,
|
||||
.server-explorer-viewlet .monaco-tree .monaco-tree-rows > .monaco-tree-row > .icon.in-progress .object-element-group:before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 36px;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: -35px;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.expanded.has-children > .content.server-group:before {
|
||||
background: url('expanded-dark.svg') 50% 50% no-repeat;
|
||||
}
|
||||
|
||||
.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content.server-group:before {
|
||||
background: url('collapsed-dark.svg') 50% 50% no-repeat;
|
||||
}
|
||||
|
||||
/* Add connection button */
|
||||
.server-explorer-viewlet .button-section {
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -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;}.cls-2{fill:#d02e00;}</style></defs><title>disconnected_server_16x16</title><path class="cls-1" d="M1.42.06V16H11a3.31,3.31,0,0,1-1.94-1H2.42V11h6a3.31,3.31,0,0,1,.54-.8,1.81,1.81,0,0,1,.19-.2H2.42v-9H11v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.43,1.79V4.24H5.89V1.79Zm2,2H3.93V2.29H5.39Z"/><path class="cls-2" d="M11.08,16a2.22,2.22,0,0,0,.45,0,2.59,2.59,0,0,0,.4,0Z"/><path class="cls-2" d="M12,9.08h0l-.39,0a3.68,3.68,0,0,0-.58,0A3.41,3.41,0,0,0,9.14,10a1.81,1.81,0,0,0-.19.2,3.46,3.46,0,0,0-.89,2.3,3.4,3.4,0,0,0,.85,2.26,1.29,1.29,0,0,0,.16.17A3.31,3.31,0,0,0,11,16H12a3.46,3.46,0,0,0,2.17-1.13,3.41,3.41,0,0,0,.88-2.3A3.47,3.47,0,0,0,12,9.08Zm0,5.72a1.72,1.72,0,0,1-.39,0,2.23,2.23,0,0,1-.77-.14,2.29,2.29,0,0,1-1.54-2.17A2.22,2.22,0,0,1,9.79,11a2.29,2.29,0,0,1,1-.67,2.23,2.23,0,0,1,.77-.14,1.72,1.72,0,0,1,.39,0h0a2.3,2.3,0,0,1,0,4.53Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1002 B |
@@ -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;}.cls-2{fill:#d02e00;}</style></defs><title>disconnected_server_inverse_16x16</title><path class="cls-1" d="M1.42.06V16H11a3.31,3.31,0,0,1-1.94-1H2.42V11h6a3.31,3.31,0,0,1,.54-.8,1.81,1.81,0,0,1,.19-.2H2.42v-9H11v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.43,1.79V4.24H5.89V1.79Zm2,2H3.93V2.29H5.39Z"/><path class="cls-2" d="M11.08,16a2.22,2.22,0,0,0,.45,0,2.59,2.59,0,0,0,.4,0Z"/><path class="cls-2" d="M12,9.08h0l-.39,0a3.68,3.68,0,0,0-.58,0A3.41,3.41,0,0,0,9.14,10a1.81,1.81,0,0,0-.19.2,3.46,3.46,0,0,0-.89,2.3,3.4,3.4,0,0,0,.85,2.26,1.29,1.29,0,0,0,.16.17A3.31,3.31,0,0,0,11,16H12a3.46,3.46,0,0,0,2.17-1.13,3.41,3.41,0,0,0,.88-2.3A3.47,3.47,0,0,0,12,9.08Zm0,5.72a1.72,1.72,0,0,1-.39,0,2.23,2.23,0,0,1-.77-.14,2.29,2.29,0,0,1-1.54-2.17A2.22,2.22,0,0,1,9.79,11a2.29,2.29,0,0,1,1-.67,2.23,2.23,0,0,1,.77-.14,1.72,1.72,0,0,1,.39,0h0a2.3,2.3,0,0,1,0,4.53Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1007 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="#E8E8E8" d="M11 10H5.344L11 4.414V10z"/></svg>
|
||||
|
After Width: | Height: | Size: 118 B |
@@ -18,11 +18,11 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
||||
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView';
|
||||
import { ServerTreeView } from 'sql/workbench/parts/objectExplorer/browser/serverTreeView';
|
||||
import {
|
||||
ActiveConnectionsFilterAction,
|
||||
AddServerAction, AddServerGroupAction
|
||||
} from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
|
||||
} from 'sql/workbench/parts/objectExplorer/browser/connectionTreeAction';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionContextKey } from 'sql/workbench/parts/connection/common/connectionContextKey';
|
||||
import { IOEShimService } from 'sql/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
||||
import { IOEShimService } from 'sql/workbench/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
||||
import { ITreeItem } from 'sql/workbench/common/views';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IOEShimService } from 'sql/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
||||
import { IOEShimService } from 'sql/workbench/parts/objectExplorer/common/objectExplorerViewTreeShim';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { ConnectionType, IConnectableInput, IConnectionCompletionOptions, IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import { IInsightTypeContrib } from 'sql/workbench/parts/dashboard/widgets/insights/interfaces';
|
||||
import { IDashboardTabContrib } from 'sql/workbench/parts/dashboard/common/dashboardTab.contribution';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
class ContributionReader {
|
||||
constructor(private manifest: IExtensionManifest) { }
|
||||
|
||||
public dashboardInsights(): IInsightTypeContrib[] {
|
||||
let contributes = this.manifest.contributes;
|
||||
if (contributes) {
|
||||
let insights: IInsightTypeContrib | IInsightTypeContrib[] = contributes['dashboard.insights'];
|
||||
if (insights) {
|
||||
if (!Array.isArray(insights)) {
|
||||
return [insights];
|
||||
}
|
||||
return insights;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public dashboardTabs(): IDashboardTabContrib[] {
|
||||
let contributes = this.manifest.contributes;
|
||||
if (contributes) {
|
||||
let tabs: IDashboardTabContrib | IDashboardTabContrib[] = contributes['dashboard.tabs'];
|
||||
if (tabs) {
|
||||
if (!Array.isArray(tabs)) {
|
||||
return [tabs];
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function renderDashboardContributions(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
|
||||
let contributionReader = new ContributionReader(manifest);
|
||||
renderDashboardTabs(onDetailsToggle, contributionReader, container);
|
||||
renderDashboardInsights(onDetailsToggle, contributionReader, container);
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderDashboardTabs(onDetailsToggle: Function, contributionReader: ContributionReader, container: HTMLElement): boolean {
|
||||
let tabs = contributionReader.dashboardTabs();
|
||||
|
||||
if (!tabs || !tabs.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const details = $('details', { open: true, ontoggle: onDetailsToggle },
|
||||
$('summary', null, localize('tabs', "Dashboard Tabs ({0})", tabs.length)),
|
||||
$('table', null,
|
||||
$('tr', null,
|
||||
$('th', null, localize('tabId', "Id")),
|
||||
$('th', null, localize('tabTitle', "Title")),
|
||||
$('th', null, localize('tabDescription', "Description"))
|
||||
),
|
||||
...tabs.map(tab => $('tr', null,
|
||||
$('td', null, $('code', null, tab.id)),
|
||||
$('td', null, tab.title ? tab.title : tab.id),
|
||||
$('td', null, tab.description),
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
append(container, details);
|
||||
return true;
|
||||
}
|
||||
|
||||
function renderDashboardInsights(onDetailsToggle: Function, contributionReader: ContributionReader, container: HTMLElement): boolean {
|
||||
let insights = contributionReader.dashboardInsights();
|
||||
|
||||
if (!insights || !insights.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const details = $('details', { open: true, ontoggle: onDetailsToggle },
|
||||
$('summary', null, localize('insights', "Dashboard Insights ({0})", insights.length)),
|
||||
$('table', null,
|
||||
$('tr', null,
|
||||
$('th', null, localize('insightId', "Id")),
|
||||
$('th', null, localize('name', "Name")),
|
||||
$('th', null, localize('insight condition', "When"))
|
||||
),
|
||||
...insights.map(insight => $('tr', null,
|
||||
$('td', null, $('code', null, insight.id)),
|
||||
$('td', null, insight.contrib.name ? insight.contrib.name : insight.id),
|
||||
$('td', null, insight.contrib.when),
|
||||
))
|
||||
)
|
||||
);
|
||||
|
||||
append(container, details);
|
||||
return true;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 4.552V13c-.028.825-.593 2-2.035 2h-8C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 3.586L8.414 3H9v.586zM12 6h-2v7h2V6zm-6 7V7.414L5.414 8H4v5h2zm1 0h2V4.414l-1 1V6h-.586L7 6.414V13z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8 5.414V6h-.586L8 5.414zM13 5v8s-.035 1-1.035 1h-8S3 14 3 13V8h1v5h2V7.414l1-1V13h2V4.414L9.414 4 9 3.586V3h-.586l-1-1h2.227L13 5zm-1 1h-2v7h2V6z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
||||
|
Before Width: | Height: | Size: 940 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M14 4.552V13c-.028.825-.593 2-2.035 2h-8C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 3.586L8.414 3H9v.586zM12 6h-2v7h2V6zm-6 7V7.414L5.414 8H4v5h2zm1 0h2V4.414l-1 1V6h-.586L7 6.414V13z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8 5.414V6h-.586L8 5.414zM13 5v8s-.035 1-1.035 1h-8S3 14 3 13V8h1v5h2V7.414l1-1V13h2V4.414L9.414 4 9 3.586V3h-.586l-1-1h2.227L13 5zm-1 1h-2v7h2V6z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
||||
|
Before Width: | Height: | Size: 940 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M16 7v8H3.964C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552V7h2z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 6h3v1H7v6H4V8h1.414L9 4.414V6zm0-3h-.586L9 3.586V3zm0 10h1v-1H9v1zm0-2h1v-1H9v1zm2 2h1v-1h-1v1zm2-3v1h1v-1h-1zm-2 1h1v-1h-1v1zm2 2h1v-1h-1v1z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8.414 3l-1-1h2.227L13 5v2h-1V6H9V4.414L9.414 4 9 3.586V3h-.586zM4 8H3v5c0 1 .964 1 .964 1H7v-1H4V8zm11 0v6H8V8h7zm-5 4H9v1h1v-1zm0-2H9v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1z" id="iconBg"/><g id="colorAction"><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1022 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 0v16H0V0h16z" id="canvas"/><path class="icon-vs-out" d="M16 7v8H3.964C3.012 15 2 14.299 2 13V8H.586l2-2H0V2h2.586l-2-2h4.828l1 1h3.608L14 4.552V7h2z" id="outline" style="display: none;"/><path class="icon-vs-fg" d="M9 6h3v1H7v6H4V8h1.414L9 4.414V6zm0-3h-.586L9 3.586V3zm0 10h1v-1H9v1zm0-2h1v-1H9v1zm2 2h1v-1h-1v1zm2-3v1h1v-1h-1zm-2 1h1v-1h-1v1zm2 2h1v-1h-1v1z" id="iconFg" style="display: none;"/><path class="icon-vs-bg" d="M8.414 3l-1-1h2.227L13 5v2h-1V6H9V4.414L9.414 4 9 3.586V3h-.586zM4 8H3v5c0 1 .964 1 .964 1H7v-1H4V8zm11 0v6H8V8h7zm-5 4H9v1h1v-1zm0-2H9v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1zm2 2h-1v1h1v-1zm0-2h-1v1h1v-1z" id="iconBg"/><g id="colorAction"><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1022 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-action-blue{fill:#00539c}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 8.38v2.258s-.992-.001-.997-.003c-.012.052-.003.14-.003.281v1.579c0 1.271-.37 2.185-1.054 2.746-.638.522-1.576.759-2.822.759H10v-3.247s1.014-.001 1.037-.003c.004-.046-.037-.117-.037-.214V11.08c0-.943.222-1.606.539-2.072C11.223 8.527 11 7.843 11 6.869V5.468c0-.087.102-.286.094-.325-.02-.002.063-.143.03-.143H10V2h1.124c1.251 0 2.193.265 2.832.81C14.633 3.387 15 4.3 15 5.522V7h.919L16 8.38zM9.414 4l-4-4H.586l2 2H0v4h2v.586L1.586 7H1v.586L.586 8H1v1.638L1.329 11H2v1.536c0 1.247.495 2.149 1.19 2.711.641.517 1.697.753 2.937.753H7v-3.247s-1.011-.001-1.033-.003c-.008-.053.033-.127.033-.228v-1.401c0-.962-.224-1.637-.542-2.111.256-.378.444-.89.511-1.564L9.414 4z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M15 8.38v1.258c-.697 0-1.046.426-1.046 1.278v1.579c0 .961-.223 1.625-.666 1.989-.445.364-1.166.547-2.164.547v-1.278c.383 0 .661-.092.834-.277s.26-.498.26-.94V11.08c0-1.089.349-1.771 1.046-2.044v-.027c-.697-.287-1.046-1-1.046-2.14V5.468c0-.793-.364-1.189-1.094-1.189V3c.993 0 1.714.19 2.16.571s.67 1.031.67 1.952v1.565c0 .861.349 1.292 1.046 1.292zm-9.967 4.142v-1.401c0-1.117-.351-1.816-1.053-2.099v-.027c.429-.175.71-.519.877-.995H3.049c-.173.247-.436.38-.805.38v1.258c.692 0 1.039.419 1.039 1.258v1.641c0 .934.226 1.584.677 1.948s1.174.547 2.167.547v-1.278c-.388 0-.666-.093-.838-.28-.17-.188-.256-.505-.256-.952z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-action-blue{fill:#75beff}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M16 8.38v2.258s-.992-.001-.997-.003c-.012.052-.003.14-.003.281v1.579c0 1.271-.37 2.185-1.054 2.746-.638.522-1.576.759-2.822.759H10v-3.247s1.014-.001 1.037-.003c.004-.046-.037-.117-.037-.214V11.08c0-.943.222-1.606.539-2.072C11.223 8.527 11 7.843 11 6.869V5.468c0-.087.102-.286.094-.325-.02-.002.063-.143.03-.143H10V2h1.124c1.251 0 2.193.265 2.832.81C14.633 3.387 15 4.3 15 5.522V7h.919L16 8.38zM9.414 4l-4-4H.586l2 2H0v4h2v.586L1.586 7H1v.586L.586 8H1v1.638L1.329 11H2v1.536c0 1.247.495 2.149 1.19 2.711.641.517 1.697.753 2.937.753H7v-3.247s-1.011-.001-1.033-.003c-.008-.053.033-.127.033-.228v-1.401c0-.962-.224-1.637-.542-2.111.256-.378.444-.89.511-1.564L9.414 4z" id="outline" style="display: none;"/><path class="icon-vs-bg" d="M15 8.38v1.258c-.697 0-1.046.426-1.046 1.278v1.579c0 .961-.223 1.625-.666 1.989-.445.364-1.166.547-2.164.547v-1.278c.383 0 .661-.092.834-.277s.26-.498.26-.94V11.08c0-1.089.349-1.771 1.046-2.044v-.027c-.697-.287-1.046-1-1.046-2.14V5.468c0-.793-.364-1.189-1.094-1.189V3c.993 0 1.714.19 2.16.571s.67 1.031.67 1.952v1.565c0 .861.349 1.292 1.046 1.292zm-9.967 4.142v-1.401c0-1.117-.351-1.816-1.053-2.099v-.027c.429-.175.71-.519.877-.995H3.049c-.173.247-.436.38-.805.38v1.258c.692 0 1.039.419 1.039 1.258v1.641c0 .934.226 1.584.677 1.948s1.174.547 2.167.547v-1.278c-.388 0-.666-.093-.838-.28-.17-.188-.256-.505-.256-.952z" id="iconBg"/><path class="icon-vs-action-blue" d="M8 4L5 7H3l2-2H1V3h4L3 1h2l3 3z" id="colorAction"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="saveXml" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<polygon id="canvas" fill="#F6F6F6" fill-rule="nonzero" opacity="0" points="16 16 0 16 0 0 16 0"></polygon>
|
||||
<polygon id="<->" fill="#424242" points="14.3701172 10.7222222 9.41870773 15 9.41870773 12.3454861 12.9578306 9.5 9.41870773 6.6640625 9.41870773 4 14.3701172 8.296875"></polygon>
|
||||
<polygon id="colorAction" fill="#00539C" fill-rule="nonzero" points="8 4 5 7 3 7 5 5 1 5 1 3 5 3 3 1 5 1"></polygon>
|
||||
<path d="M2.32152009,8 L5.28619938,8 L3.4164898,9.5 L6.95140946,12.3454861 L6.95140946,15 L2,10.7222222 L2,8.27777778 L2.32152009,8 Z" id="Combined-Shape" fill="#424242"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 877 B |
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="saveXmlInverse" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<polygon id="canvas" fill="#F6F6F6" fill-rule="nonzero" opacity="0" points="16 16 0 16 0 0 16 0"></polygon>
|
||||
<polygon id="<->" fill="#C5C5C5" points="14.3701172 10.7222222 9.41870773 15 9.41870773 12.3454861 12.9578306 9.5 9.41870773 6.6640625 9.41870773 4 14.3701172 8.296875"></polygon>
|
||||
<polygon id="colorAction" fill="#75BEFF" fill-rule="nonzero" points="8 4 5 7 3 7 5 5 1 5 1 3 5 3 3 1 5 1"></polygon>
|
||||
<path d="M2.32152009,8 L5.28619938,8 L3.4164898,9.5 L6.95140946,12.3454861 L6.95140946,15 L2,10.7222222 L2,8.27777778 L2.32152009,8 Z" id="Combined-Shape" fill="#C5C5C5"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 884 B |
@@ -1 +0,0 @@
|
||||
<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:#c91f1f;}</style></defs><title>view_as_chart_16x16</title><rect x="0.5" y="14.22" width="14.99" height="1"/><rect x="13.52" y="0.2" width="1" height="13.03"/><path d="M2,12.34a.71.71,0,0,0,.44-.15v1h-1V12.1A.74.74,0,0,0,2,12.34Z"/><path d="M5.52,6.22v.29a2.58,2.58,0,0,1-.14-.29Z"/><polygon points="4.52 10.16 5.52 9.16 5.52 13.22 4.52 13.22 4.52 10.16"/><path d="M7.54,8.74a3.68,3.68,0,0,0,1,.18v4.3h-1Z"/><path d="M10.93,8.36a3.07,3.07,0,0,0,.59-.43v5.31h-1V8.57A3.18,3.18,0,0,0,10.93,8.36Z"/><path class="cls-1" d="M5.38,6.22a2.58,2.58,0,0,0,.14.29V6.22Z"/><path class="cls-1" d="M5.38,6.22a2.58,2.58,0,0,0,.14.29V6.22Z"/><path d="M12.42,3.76a3.71,3.71,0,0,0-.37-.89A3.85,3.85,0,0,0,10.7,1.52a3.35,3.35,0,0,0-.89-.37,3.78,3.78,0,0,0-2.88.37,4.3,4.3,0,0,0-.76.59,3.92,3.92,0,0,0-.58.76,3.73,3.73,0,0,0-.38.89,3.63,3.63,0,0,0-.13,1A3.61,3.61,0,0,0,5.3,6a1.19,1.19,0,0,0,.08.2h.14v.29a4.32,4.32,0,0,0,.42.63,4,4,0,0,1-.3.3L5.1,8l-.58.56-.11.11-.76.75-.73.74-.47.48-.16.18q-.29.29-.45.48a.67.67,0,0,0-.16.24.41.41,0,0,0,.09.26.31.31,0,0,0,.25.11.33.33,0,0,0,.24-.1l.2-.2,2.06-2,1-1,.91-.91a3.87,3.87,0,0,0,1.11.64,3.37,3.37,0,0,0,1,.21h.28a3.64,3.64,0,0,0,1-.13,3.41,3.41,0,0,0,.71-.29L10.7,8a3.61,3.61,0,0,0,.76-.59l.06-.07a3.46,3.46,0,0,0,.53-.68,4.08,4.08,0,0,0,.37-.89,3.61,3.61,0,0,0,0-2Zm-.79,2.18a1.63,1.63,0,0,1-.11.23,3.13,3.13,0,0,1-.54.74,3.32,3.32,0,0,1-.46.38,3.15,3.15,0,0,1-.51.28,3,3,0,0,1-1.19.24H8.54a3.12,3.12,0,0,1-.91-.23l-.09,0a3,3,0,0,1-.88-.62,3,3,0,0,1-.9-2.16A3,3,0,0,1,6,3.56a3.09,3.09,0,0,1,.66-1,3,3,0,0,1,1-.65,2.82,2.82,0,0,1,1.19-.25A2.86,2.86,0,0,1,10,1.94a4.12,4.12,0,0,1,.51.27,3.31,3.31,0,0,1,.46.38,3.6,3.6,0,0,1,.54.74,1.63,1.63,0,0,1,.11.23,2.82,2.82,0,0,1,.25,1.19A2.82,2.82,0,0,1,11.63,5.94Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1 +0,0 @@
|
||||
<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>view_as_chart_inverse_16x16</title><rect class="cls-1" x="0.5" y="14.43" width="14.99" height="1"/><rect class="cls-1" x="13.52" y="0.41" width="1" height="13.03"/><path class="cls-1" d="M2,12.55a.71.71,0,0,0,.44-.15v1h-1V12.31A.74.74,0,0,0,2,12.55Z"/><path class="cls-1" d="M5.52,6.43v.29a2.58,2.58,0,0,1-.14-.29Z"/><polygon class="cls-1" points="4.52 10.37 5.52 9.37 5.52 13.43 4.52 13.43 4.52 10.37"/><path class="cls-1" d="M7.54,8.95a3.68,3.68,0,0,0,1,.18v4.3h-1Z"/><path class="cls-1" d="M10.93,8.57a3.07,3.07,0,0,0,.59-.43v5.31h-1V8.78A3.18,3.18,0,0,0,10.93,8.57Z"/><path class="cls-1" d="M5.38,6.43a2.58,2.58,0,0,0,.14.29V6.43Z"/><path class="cls-1" d="M5.38,6.43a2.58,2.58,0,0,0,.14.29V6.43Z"/><path class="cls-1" d="M12.42,4a3.71,3.71,0,0,0-.37-.89A3.85,3.85,0,0,0,10.7,1.73a3.35,3.35,0,0,0-.89-.37,3.78,3.78,0,0,0-2.88.37,4.3,4.3,0,0,0-.76.59,3.92,3.92,0,0,0-.58.76A3.73,3.73,0,0,0,5.21,4a3.63,3.63,0,0,0-.13,1A3.61,3.61,0,0,0,5.3,6.23a1.19,1.19,0,0,0,.08.2h.14v.29a4.32,4.32,0,0,0,.42.63,4,4,0,0,1-.3.3l-.54.54-.58.56-.11.11-.76.75-.73.74-.47.48L2.29,11q-.29.29-.45.48a.67.67,0,0,0-.16.24.41.41,0,0,0,.09.26A.31.31,0,0,0,2,12.1a.33.33,0,0,0,.24-.1l.2-.2,2.06-2,1-1,.91-.91a3.87,3.87,0,0,0,1.11.64,3.37,3.37,0,0,0,1,.21h.28a3.64,3.64,0,0,0,1-.13,3.41,3.41,0,0,0,.71-.29l.18-.09a3.61,3.61,0,0,0,.76-.59l.06-.07a3.46,3.46,0,0,0,.53-.68A4.08,4.08,0,0,0,12.42,6a3.61,3.61,0,0,0,0-2Zm-.79,2.18a1.63,1.63,0,0,1-.11.23,3.13,3.13,0,0,1-.54.74,3.32,3.32,0,0,1-.46.38,3.15,3.15,0,0,1-.51.28A3,3,0,0,1,8.82,8H8.54a3.12,3.12,0,0,1-.91-.23l-.09,0a3,3,0,0,1-.88-.62A3,3,0,0,1,5.76,5,3,3,0,0,1,6,3.77a3.09,3.09,0,0,1,.66-1,3,3,0,0,1,1-.65A2.82,2.82,0,0,1,8.82,1.9,2.86,2.86,0,0,1,10,2.15a4.12,4.12,0,0,1,.51.27A3.31,3.31,0,0,1,11,2.8a3.6,3.6,0,0,1,.54.74,1.63,1.63,0,0,1,.11.23A2.82,2.82,0,0,1,11.88,5,2.82,2.82,0,0,1,11.63,6.15Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,377 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Action } from 'vs/base/common/actions';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||
import { ServerTreeView } from 'sql/workbench/parts/objectExplorer/browser/serverTreeView';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import * as Constants from 'sql/platform/connection/common/constants';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { ObjectExplorerActionsContext } from 'sql/workbench/parts/objectExplorer/browser/objectExplorerActions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
|
||||
export class RefreshAction extends Action {
|
||||
|
||||
public static ID = 'objectExplorer.refresh';
|
||||
public static LABEL = localize('connectionTree.refresh', 'Refresh');
|
||||
private _tree: ITree;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
tree: ITree,
|
||||
private element: IConnectionProfile | TreeNode,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService
|
||||
) {
|
||||
super(id, label);
|
||||
this._tree = tree;
|
||||
}
|
||||
public run(): Promise<boolean> {
|
||||
var treeNode: TreeNode;
|
||||
if (this.element instanceof ConnectionProfile) {
|
||||
let connection: ConnectionProfile = this.element;
|
||||
if (this._connectionManagementService.isConnected(undefined, connection)) {
|
||||
treeNode = this._objectExplorerService.getObjectExplorerNode(connection);
|
||||
if (treeNode === undefined) {
|
||||
this._objectExplorerService.updateObjectExplorerNodes(connection.toIConnectionProfile()).then(() => {
|
||||
treeNode = this._objectExplorerService.getObjectExplorerNode(connection);
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (this.element instanceof TreeNode) {
|
||||
treeNode = this.element;
|
||||
}
|
||||
|
||||
if (treeNode) {
|
||||
return this._tree.collapse(this.element).then(() => {
|
||||
return this._objectExplorerService.refreshTreeNode(treeNode.getSession(), treeNode).then(() => {
|
||||
|
||||
return this._tree.refresh(this.element).then(() => {
|
||||
return this._tree.expand(this.element);
|
||||
}, refreshError => {
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
}, error => {
|
||||
this.showError(error);
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
}, collapseError => {
|
||||
return Promise.resolve(true);
|
||||
});
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private showError(errorMessage: string) {
|
||||
if (this._errorMessageService) {
|
||||
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class DisconnectConnectionAction extends Action {
|
||||
public static ID = 'objectExplorer.disconnect';
|
||||
public static LABEL = localize('DisconnectAction', 'Disconnect');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private _connectionProfile: ConnectionProfile,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(actionContext: ObjectExplorerActionsContext): Promise<any> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
if (!this._connectionProfile) {
|
||||
resolve(true);
|
||||
}
|
||||
if (this._connectionManagementService.isProfileConnected(this._connectionProfile)) {
|
||||
let profileImpl = this._connectionProfile as ConnectionProfile;
|
||||
if (profileImpl) {
|
||||
profileImpl.isDisconnecting = true;
|
||||
}
|
||||
this._connectionManagementService.disconnect(this._connectionProfile).then((value) => {
|
||||
if (profileImpl) {
|
||||
profileImpl.isDisconnecting = false;
|
||||
}
|
||||
resolve(true);
|
||||
}
|
||||
).catch(disconnectError => {
|
||||
reject(disconnectError);
|
||||
});
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to add a server to the group
|
||||
*/
|
||||
export class AddServerAction extends Action {
|
||||
public static ID = 'registeredServers.addConnection';
|
||||
public static LABEL = localize('connectionTree.addConnection', 'New Connection');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'add-server-action';
|
||||
}
|
||||
|
||||
public run(element: ConnectionProfileGroup): Promise<boolean> {
|
||||
let connection: IConnectionProfile = element === undefined ? undefined : {
|
||||
connectionName: undefined,
|
||||
serverName: undefined,
|
||||
databaseName: undefined,
|
||||
userName: undefined,
|
||||
password: undefined,
|
||||
authenticationType: undefined,
|
||||
groupId: undefined,
|
||||
groupFullName: element.fullName,
|
||||
savePassword: undefined,
|
||||
getOptionsKey: undefined,
|
||||
matches: undefined,
|
||||
providerName: '',
|
||||
options: {},
|
||||
saveProfile: true,
|
||||
id: element.id
|
||||
};
|
||||
this._connectionManagementService.showConnectionDialog(undefined, connection);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to add a server to the group
|
||||
*/
|
||||
export class AddServerGroupAction extends Action {
|
||||
public static ID = 'registeredServers.addServerGroup';
|
||||
public static LABEL = localize('connectionTree.addServerGroup', 'New Server Group');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'add-server-group-action';
|
||||
}
|
||||
|
||||
public run(): Promise<boolean> {
|
||||
this._connectionManagementService.showCreateServerGroupDialog();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to edit a server group
|
||||
*/
|
||||
export class EditServerGroupAction extends Action {
|
||||
public static ID = 'registeredServers.editServerGroup';
|
||||
public static LABEL = localize('connectionTree.editServerGroup', 'Edit Server Group');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private _group: ConnectionProfileGroup,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'edit-server-group-action';
|
||||
}
|
||||
|
||||
public run(): Promise<boolean> {
|
||||
this._connectionManagementService.showEditServerGroupDialog(this._group);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display active connections in the tree
|
||||
*/
|
||||
export class ActiveConnectionsFilterAction extends Action {
|
||||
public static ID = 'registeredServers.recentConnections';
|
||||
public static LABEL = localize('activeConnections', 'Show Active Connections');
|
||||
private static enabledClass = 'active-connections-action';
|
||||
private static disabledClass = 'icon server-page';
|
||||
private static showAllConnectionsLabel = localize('showAllConnections', 'Show All Connections');
|
||||
private _isSet: boolean;
|
||||
public static readonly ACTIVE = 'active';
|
||||
public get isSet(): boolean {
|
||||
return this._isSet;
|
||||
}
|
||||
public set isSet(value: boolean) {
|
||||
this._isSet = value;
|
||||
this.class = (!this._isSet) ?
|
||||
ActiveConnectionsFilterAction.enabledClass : ActiveConnectionsFilterAction.disabledClass;
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private view: ServerTreeView,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = ActiveConnectionsFilterAction.enabledClass;
|
||||
}
|
||||
|
||||
public run(): Promise<boolean> {
|
||||
if (!this.view) {
|
||||
// return without doing anything
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
if (this.class === ActiveConnectionsFilterAction.enabledClass) {
|
||||
// show active connections in the tree
|
||||
this.view.showFilteredTree(ActiveConnectionsFilterAction.ACTIVE);
|
||||
this.isSet = true;
|
||||
this.label = ActiveConnectionsFilterAction.showAllConnectionsLabel;
|
||||
} else {
|
||||
// show full tree
|
||||
this.view.refreshTree();
|
||||
this.isSet = false;
|
||||
this.label = ActiveConnectionsFilterAction.LABEL;
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display recent connections in the tree
|
||||
*/
|
||||
export class RecentConnectionsFilterAction extends Action {
|
||||
public static ID = 'registeredServers.recentConnections';
|
||||
public static LABEL = localize('recentConnections', 'Recent Connections');
|
||||
private static enabledClass = 'recent-connections-action';
|
||||
private static disabledClass = 'recent-connections-action-set';
|
||||
private _isSet: boolean;
|
||||
public get isSet(): boolean {
|
||||
return this._isSet;
|
||||
}
|
||||
public set isSet(value: boolean) {
|
||||
this._isSet = value;
|
||||
this.class = (!this._isSet) ?
|
||||
RecentConnectionsFilterAction.enabledClass : RecentConnectionsFilterAction.disabledClass;
|
||||
}
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private view: ServerTreeView,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = RecentConnectionsFilterAction.enabledClass;
|
||||
this._isSet = false;
|
||||
}
|
||||
|
||||
public run(): Promise<boolean> {
|
||||
if (!this.view) {
|
||||
// return without doing anything
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
if (this.class === RecentConnectionsFilterAction.enabledClass) {
|
||||
// show recent connections in the tree
|
||||
this.view.showFilteredTree('recent');
|
||||
this.isSet = true;
|
||||
} else {
|
||||
// show full tree
|
||||
this.view.refreshTree();
|
||||
this.isSet = false;
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class NewQueryAction extends Action {
|
||||
public static ID = 'registeredServers.newQuery';
|
||||
public static LABEL = localize('registeredServers.newQuery', 'New Query');
|
||||
private _connectionProfile: IConnectionProfile;
|
||||
get connectionProfile(): IConnectionProfile {
|
||||
return this._connectionProfile;
|
||||
}
|
||||
set connectionProfile(profile: IConnectionProfile) {
|
||||
this._connectionProfile = profile;
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQueryEditorService private queryEditorService: IQueryEditorService,
|
||||
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
|
||||
@IObjectExplorerService protected _objectExplorerService: IObjectExplorerService,
|
||||
@IEditorService protected _workbenchEditorService: IEditorService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'extension-action update';
|
||||
}
|
||||
|
||||
public run(actionContext: ObjectExplorerActionsContext): Promise<boolean> {
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
this._connectionProfile = new ConnectionProfile(this._capabilitiesService, actionContext.connectionProfile);
|
||||
}
|
||||
|
||||
TaskUtilities.newQuery(this._connectionProfile, this.connectionManagementService, this.queryEditorService, this._objectExplorerService, this._workbenchEditorService);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to delete a server/group
|
||||
*/
|
||||
export class DeleteConnectionAction extends Action {
|
||||
public static ID = 'registeredServers.deleteConnection';
|
||||
public static DELETE_CONNECTION_LABEL = localize('deleteConnection', 'Delete Connection');
|
||||
public static DELETE_CONNECTION_GROUP_LABEL = localize('deleteConnectionGroup', 'Delete Group');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private element: IConnectionProfile | ConnectionProfileGroup,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
super(id, label);
|
||||
this.class = 'delete-connection-action';
|
||||
if (element instanceof ConnectionProfileGroup && element.id === Constants.unsavedGroupId) {
|
||||
this.enabled = false;
|
||||
}
|
||||
|
||||
if (element instanceof ConnectionProfile) {
|
||||
let parent: ConnectionProfileGroup = element.parent;
|
||||
if (parent && parent.id === Constants.unsavedGroupId) {
|
||||
this.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public run(): Promise<boolean> {
|
||||
if (this.element instanceof ConnectionProfile) {
|
||||
this._connectionManagementService.deleteConnection(this.element);
|
||||
} else if (this.element instanceof ConnectionProfileGroup) {
|
||||
this._connectionManagementService.deleteConnectionGroup(this.element);
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ITree, IDragAndDrop, IDragOverReaction, DRAG_OVER_ACCEPT_BUBBLE_DOWN, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
|
||||
import * as Constants from 'sql/platform/connection/common/constants';
|
||||
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
|
||||
/**
|
||||
* Implements drag and drop for the server tree
|
||||
*/
|
||||
export class ServerTreeDragAndDrop implements IDragAndDrop {
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a uri if the given element should be allowed to drag.
|
||||
* Returns null, otherwise.
|
||||
*/
|
||||
public getDragURI(tree: ITree, element: any): string {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return (<ConnectionProfile>element).id;
|
||||
}
|
||||
else if (element instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>element).id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a label(name) to display when dragging the element.
|
||||
*/
|
||||
public getDragLabel(tree: ITree, elements: any[]): string {
|
||||
if (elements[0] instanceof ConnectionProfile) {
|
||||
return (<ConnectionProfile>elements[0]).serverName;
|
||||
} else if (elements[0] instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>elements[0]).name;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the drag operation starts.
|
||||
*/
|
||||
public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void {
|
||||
TreeUpdateUtils.isInDragAndDrop = true;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DragOverReaction indicating whether sources can be
|
||||
* dropped into target or some parent of the target.
|
||||
* Returns DRAG_OVER_ACCEPT_BUBBLE_DOWN when element is a connection group or connection
|
||||
*/
|
||||
public onDragOver(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): IDragOverReaction {
|
||||
|
||||
let canDragOver: boolean = true;
|
||||
if (targetElement instanceof ConnectionProfile || targetElement instanceof ConnectionProfileGroup) {
|
||||
let targetConnectionProfileGroup = this.getTargetGroup(targetElement);
|
||||
// Verify if the connection can be moved to the target group
|
||||
const source = data.getData()[0];
|
||||
if (source instanceof ConnectionProfile) {
|
||||
if (!this._connectionManagementService.canChangeConnectionConfig(source, targetConnectionProfileGroup.id)) {
|
||||
canDragOver = false;
|
||||
}
|
||||
} else if (source instanceof ConnectionProfileGroup) {
|
||||
// Dropping a group to itself or its descendants nodes is not allowed
|
||||
// to avoid creating a circular structure.
|
||||
canDragOver = source.id !== targetElement.id && !source.isAncestorOf(targetElement);
|
||||
}
|
||||
|
||||
} else {
|
||||
canDragOver = false;
|
||||
}
|
||||
|
||||
if (canDragOver) {
|
||||
return DRAG_OVER_ACCEPT_BUBBLE_DOWN(true);
|
||||
} else {
|
||||
return DRAG_OVER_REJECT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a drop in the server tree.
|
||||
*/
|
||||
public drop(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): void {
|
||||
TreeUpdateUtils.isInDragAndDrop = false;
|
||||
|
||||
let targetConnectionProfileGroup: ConnectionProfileGroup = this.getTargetGroup(targetElement);
|
||||
|
||||
const source = data.getData()[0];
|
||||
if (source && source.getParent) {
|
||||
let oldParent: ConnectionProfileGroup = source.getParent();
|
||||
const self = this;
|
||||
if (this.isDropAllowed(targetConnectionProfileGroup, oldParent, source)) {
|
||||
|
||||
if (source instanceof ConnectionProfile) {
|
||||
// Change group id of profile
|
||||
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id).then(() => {
|
||||
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
|
||||
});
|
||||
} else if (source instanceof ConnectionProfileGroup) {
|
||||
// Change parent id of group
|
||||
this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(() => {
|
||||
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dropAbort(tree: ITree, data: IDragAndDropData): void {
|
||||
TreeUpdateUtils.isInDragAndDrop = false;
|
||||
}
|
||||
|
||||
private getTargetGroup(targetElement: any): ConnectionProfileGroup {
|
||||
let targetConnectionProfileGroup: ConnectionProfileGroup;
|
||||
if (targetElement instanceof ConnectionProfile) {
|
||||
targetConnectionProfileGroup = (<ConnectionProfile>targetElement).getParent();
|
||||
}
|
||||
else {
|
||||
targetConnectionProfileGroup = <ConnectionProfileGroup>targetElement;
|
||||
}
|
||||
|
||||
return targetConnectionProfileGroup;
|
||||
}
|
||||
|
||||
private isDropAllowed(targetConnectionProfileGroup: ConnectionProfileGroup,
|
||||
oldParent: ConnectionProfileGroup,
|
||||
source: ConnectionProfile | ConnectionProfileGroup): boolean {
|
||||
|
||||
let isDropToItself = source && targetConnectionProfileGroup && (source instanceof ConnectionProfileGroup) && source.name === targetConnectionProfileGroup.name;
|
||||
let isDropToSameLevel = oldParent && oldParent.equals(targetConnectionProfileGroup);
|
||||
let isUnsavedDrag = source && (source instanceof ConnectionProfileGroup) && (source.id === Constants.unsavedGroupId);
|
||||
return (!isDropToSameLevel && !isDropToItself && !isUnsavedDrag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements drag and drop for the connection tree
|
||||
*/
|
||||
export class RecentConnectionsDragAndDrop implements IDragAndDrop {
|
||||
|
||||
/**
|
||||
* Returns a uri if the given element should be allowed to drag.
|
||||
* Returns null, otherwise.
|
||||
*/
|
||||
public getDragURI(tree: ITree, element: any): string {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return (<ConnectionProfile>element).id;
|
||||
}
|
||||
else if (element instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>element).id;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a label(name) to display when dragging the element.
|
||||
*/
|
||||
public getDragLabel(tree: ITree, elements: any[]): string {
|
||||
if (elements[0] instanceof ConnectionProfile) {
|
||||
return (<ConnectionProfile>elements[0]).serverName;
|
||||
}
|
||||
else if (elements[0] instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>elements[0]).name;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sent when the drag operation is starting.
|
||||
*/
|
||||
public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a DragOverReaction indicating whether sources can be
|
||||
* dropped into target or some parent of the target.
|
||||
*/
|
||||
public onDragOver(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): IDragOverReaction {
|
||||
return DRAG_OVER_REJECT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle drop in the server tree.
|
||||
*/
|
||||
public drop(tree: ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): void {
|
||||
// No op
|
||||
}
|
||||
|
||||
public dropAbort(tree: ITree, data: IDragAndDropData): void { }
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>add_server_16x16</title><path d="M1.37.16V15.84H9.89v-1H2.37V10.13H10.5v.46h1V.16Zm9.13,9H2.37v-8H10.5Z"/><path d="M6,4.68H3.47V2.17H6Zm-2-.5H5.49V2.67H4Z"/><path d="M11.88,15.84V13.91H9.95v-.82h1.93V11.16h.82v1.93h1.93v.82H12.7v1.93Z"/></svg>
|
||||
|
After Width: | Height: | Size: 343 B |
@@ -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>add_server_inverse_16x16</title><path class="cls-1" d="M.23.16V15.84H8.75v-1H1.23V10.13H9.36v.46h1V.16Zm9.13,9H1.23v-8H9.36Z"/><path class="cls-1" d="M4.84,4.69H2.32V2.17H4.84Zm-2-.5H4.34V2.67H2.82Z"/><path class="cls-1" d="M10.73,15.84V13.91H8.8v-.82h1.93V11.16h.82v1.93h1.93v.82H11.55v1.93Z"/></svg>
|
||||
|
After Width: | Height: | Size: 447 B |
@@ -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;}.cls-2{fill:#3bb44a;}</style></defs><title>connected_active_server_16x16</title><path class="cls-1" d="M1.29.07V16h9.59a3.31,3.31,0,0,1-1.94-1H2.29V11h6a3.31,3.31,0,0,1,.54-.8A1.81,1.81,0,0,1,9,10H2.29v-9h8.53v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.3,1.8V4.25H5.75V1.8Zm2,2H3.8V2.3H5.25Z"/><circle class="cls-2" cx="11.24" cy="12.52" r="3.47"/></svg>
|
||||
|
After Width: | Height: | Size: 502 B |
@@ -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;}.cls-2{fill:#3bb44a;}</style></defs><title>connected_active_server_inverse_16x16</title><path class="cls-1" d="M1.29.07V16h9.59a3.31,3.31,0,0,1-1.94-1H2.29V11h6a3.31,3.31,0,0,1,.54-.8A1.81,1.81,0,0,1,9,10H2.29v-9h8.53v8a3.68,3.68,0,0,1,.58,0l.39,0h0v-9Z"/><path class="cls-1" d="M3.3,1.8V4.25H5.75V1.8Zm2,2H3.8V2.3H5.25Z"/><circle class="cls-2" cx="11.24" cy="12.52" r="3.47"/></svg>
|
||||
|
After Width: | Height: | Size: 507 B |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>new_servergroup_16x16</title><path d="M10.2,13h-5v-.75a1.12,1.12,0,0,1,.07-.43,1.81,1.81,0,0,1,.18-.34,3.89,3.89,0,0,1,.24-.32,2.14,2.14,0,0,0,.24-.36,2.26,2.26,0,0,0,.18-.45,2.16,2.16,0,0,0,.07-.61v-5a1,1,0,0,0-.07-.39A1,1,0,0,0,5.87,4l-3-3h8.29V7.08h1V1a1,1,0,0,0-.08-.39,1,1,0,0,0-.54-.54A1,1,0,0,0,11.16,0h-10V13.41L3.45,15.7a1,1,0,0,0,.71.3,1,1,0,0,0,.38-.08A1,1,0,0,0,5,15.56,1,1,0,0,0,5.16,15V14h5ZM5.09,10.18a1.81,1.81,0,0,1-.18.34,3.89,3.89,0,0,1-.24.32,2.14,2.14,0,0,0-.24.36,2.26,2.26,0,0,0-.18.45,2.16,2.16,0,0,0-.07.61V15l-2-2V1.71l3,3v5A1.12,1.12,0,0,1,5.09,10.18Z"/><path d="M11,12.67V10.75H9.11V9.93H11V8h.82V9.93h1.93v.82H11.85v1.93Z"/></svg>
|
||||
|
After Width: | Height: | Size: 759 B |
@@ -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>new_servergroup_inverse_16x16</title><path class="cls-1" d="M10.2,13h-5v-.75a1.12,1.12,0,0,1,.07-.43,1.81,1.81,0,0,1,.18-.34,3.89,3.89,0,0,1,.24-.32,2.14,2.14,0,0,0,.24-.36,2.26,2.26,0,0,0,.18-.45,2.16,2.16,0,0,0,.07-.61v-5a1,1,0,0,0-.07-.39A1,1,0,0,0,5.87,4l-3-3h8.29V7.08h1V1a1,1,0,0,0-.08-.39,1,1,0,0,0-.54-.54A1,1,0,0,0,11.16,0h-10V13.41L3.45,15.7a1,1,0,0,0,.71.3,1,1,0,0,0,.38-.08A1,1,0,0,0,5,15.56,1,1,0,0,0,5.16,15V14h5ZM5.09,10.18a1.81,1.81,0,0,1-.18.34,3.89,3.89,0,0,1-.24.32,2.14,2.14,0,0,0-.24.36,2.26,2.26,0,0,0-.18.45,2.16,2.16,0,0,0-.07.61V15l-2-2V1.71l3,3v5A1.12,1.12,0,0,1,5.09,10.18Z"/><path class="cls-1" d="M11,12.67V10.75H9.11V9.93H11V8h.82V9.93h1.93v.82H11.85v1.93Z"/></svg>
|
||||
|
After Width: | Height: | Size: 841 B |
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.group-color-options {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.group-color-options .server-group-color {
|
||||
flex: 1 1 auto;
|
||||
height: 20px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.server-group-dialog {
|
||||
padding: 15px
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Icons for various registered servers actions */
|
||||
.monaco-workbench .add-server-action {
|
||||
background-image: url('add_server.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .add-server-action,
|
||||
.hc-black .monaco-workbench .add-server-action {
|
||||
background-image: url('add_server_inverse.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .add-server-group-action {
|
||||
background-image: url('new_servergroup.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .add-server-group-action,
|
||||
.hc-black .monaco-workbench .add-server-group-action {
|
||||
background-image: url('new_servergroup_inverse.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .active-connections-action {
|
||||
background-image: url('connected_active_server.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .active-connections-action,
|
||||
.hc-black .monaco-workbench .active-connections-action{
|
||||
background-image: url('connected_active_server_inverse.svg');
|
||||
}
|
||||
@@ -0,0 +1,413 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { Action } from 'vs/base/common/actions';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ExecuteCommandAction } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { IConnectionManagementService, IConnectionCompletionOptions } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import {
|
||||
ScriptSelectAction, EditDataAction, ScriptCreateAction,
|
||||
ScriptExecuteAction, ScriptDeleteAction, ScriptAlterAction
|
||||
} from 'sql/workbench/common/actions';
|
||||
import { NodeType } from 'sql/workbench/parts/objectExplorer/common/nodeType';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
import { TreeSelectionHandler } from 'sql/workbench/parts/objectExplorer/browser/treeSelectionHandler';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IScriptingService } from 'sql/platform/scripting/common/scriptingService';
|
||||
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import * as Constants from 'sql/platform/connection/common/constants';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
|
||||
|
||||
export class ObjectExplorerActionsContext implements azdata.ObjectExplorerContext {
|
||||
public connectionProfile: azdata.IConnectionProfile;
|
||||
public nodeInfo: azdata.NodeInfo;
|
||||
public isConnectionNode: boolean = false;
|
||||
}
|
||||
|
||||
async function getTreeNode(context: ObjectExplorerActionsContext, objectExplorerService: IObjectExplorerService): Promise<TreeNode> {
|
||||
if (context.isConnectionNode) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return await objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath);
|
||||
}
|
||||
|
||||
|
||||
export class OEAction extends ExecuteCommandAction {
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@ICommandService commandService: ICommandService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
|
||||
) {
|
||||
super(id, label, commandService);
|
||||
}
|
||||
|
||||
public async run(actionContext: any): Promise<boolean> {
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
|
||||
|
||||
let profile: IConnectionProfile;
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
if (actionContext.isConnectionNode) {
|
||||
profile = new ConnectionProfile(this._capabilitiesService, actionContext.connectionProfile);
|
||||
} else {
|
||||
// Get the "correct" version from the tree
|
||||
let treeNode = await getTreeNode(actionContext, this._objectExplorerService);
|
||||
profile = TreeUpdateUtils.getConnectionProfile(treeNode);
|
||||
}
|
||||
}
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
|
||||
return super.run(profile).then(() => {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ManageConnectionAction extends Action {
|
||||
public static ID = 'objectExplorer.manage';
|
||||
public static LABEL = localize('ManageAction', 'Manage');
|
||||
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
private _tree: ITree,
|
||||
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
|
||||
@ICapabilitiesService protected _capabilitiesService: ICapabilitiesService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(actionContext: ObjectExplorerActionsContext): Promise<any> {
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
let self = this;
|
||||
let promise = new Promise<boolean>((resolve, reject) => {
|
||||
self.doManage(actionContext).then((success) => {
|
||||
self.done();
|
||||
resolve(success);
|
||||
}, error => {
|
||||
self.done();
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
private async doManage(actionContext: ObjectExplorerActionsContext): Promise<boolean> {
|
||||
let treeNode: TreeNode = undefined;
|
||||
let connectionProfile: IConnectionProfile = undefined;
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
// Must use a real connection profile for this action due to lookup
|
||||
connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, actionContext.connectionProfile);
|
||||
if (!actionContext.isConnectionNode) {
|
||||
treeNode = await getTreeNode(actionContext, this._objectExplorerService);
|
||||
if (TreeUpdateUtils.isDatabaseNode(treeNode)) {
|
||||
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!connectionProfile) {
|
||||
// This should never happen. There should be always a valid connection if the manage action is called for
|
||||
// an OE node or a database node
|
||||
return true;
|
||||
}
|
||||
|
||||
let options: IConnectionCompletionOptions = {
|
||||
params: undefined,
|
||||
saveTheConnection: false,
|
||||
showConnectionDialogOnError: true,
|
||||
showDashboard: true,
|
||||
showFirewallRuleOnError: true
|
||||
};
|
||||
|
||||
// If it's a database node just open a database connection and open dashboard,
|
||||
// the node is already from an open OE session we don't need to create new session
|
||||
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
|
||||
return this._connectionManagementService.showDashboard(connectionProfile);
|
||||
} else {
|
||||
return TreeUpdateUtils.connectAndCreateOeSession(connectionProfile, options, this._connectionManagementService, this._objectExplorerService, this._tree);
|
||||
}
|
||||
}
|
||||
|
||||
private done() {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export class OEScriptSelectAction extends ScriptSelectAction {
|
||||
public static ID = 'objectExplorer.' + ScriptSelectAction.ID;
|
||||
private _objectExplorerTreeNode: TreeNode;
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
|
||||
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
|
||||
@IScriptingService protected _scriptingService: IScriptingService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService);
|
||||
}
|
||||
|
||||
public async run(actionContext: any): Promise<boolean> {
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
//set objectExplorerTreeNode for context menu clicks
|
||||
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
|
||||
}
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode);
|
||||
var ownerUri = this._connectionManagementService.getConnectionUri(connectionProfile);
|
||||
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
|
||||
var metadata = this._objectExplorerTreeNode.metadata;
|
||||
|
||||
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class OEEditDataAction extends EditDataAction {
|
||||
public static ID = 'objectExplorer.' + EditDataAction.ID;
|
||||
private _objectExplorerTreeNode: TreeNode;
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
|
||||
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
|
||||
@IScriptingService protected _scriptingService: IScriptingService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService
|
||||
) {
|
||||
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService);
|
||||
}
|
||||
|
||||
public async run(actionContext: any): Promise<boolean> {
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
//set objectExplorerTreeNode for context menu clicks
|
||||
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
|
||||
}
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
|
||||
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
|
||||
|
||||
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class OEScriptCreateAction extends ScriptCreateAction {
|
||||
public static ID = 'objectExplorer.' + ScriptCreateAction.ID;
|
||||
private _objectExplorerTreeNode: TreeNode;
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
|
||||
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
|
||||
@IScriptingService protected _scriptingService: IScriptingService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IErrorMessageService protected _errorMessageService: IErrorMessageService
|
||||
) {
|
||||
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
|
||||
}
|
||||
|
||||
public async run(actionContext: any): Promise<boolean> {
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
//set objectExplorerTreeNode for context menu clicks
|
||||
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
|
||||
}
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
|
||||
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
|
||||
var ownerUri = this._connectionManagementService.getConnectionUri(connectionProfile);
|
||||
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
|
||||
|
||||
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class OEScriptExecuteAction extends ScriptExecuteAction {
|
||||
public static ID = 'objectExplorer.' + ScriptExecuteAction.ID;
|
||||
private _objectExplorerTreeNode: TreeNode;
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
|
||||
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
|
||||
@IScriptingService protected _scriptingService: IScriptingService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IErrorMessageService protected _errorMessageService: IErrorMessageService
|
||||
) {
|
||||
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
|
||||
}
|
||||
|
||||
public async run(actionContext: any): Promise<boolean> {
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
//set objectExplorerTreeNode for context menu clicks
|
||||
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
|
||||
}
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
|
||||
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
|
||||
var ownerUri = this._connectionManagementService.getConnectionUri(connectionProfile);
|
||||
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
|
||||
|
||||
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class OEScriptAlterAction extends ScriptAlterAction {
|
||||
public static ID = 'objectExplorer.' + ScriptAlterAction.ID;
|
||||
private _objectExplorerTreeNode: TreeNode;
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
|
||||
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
|
||||
@IScriptingService protected _scriptingService: IScriptingService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IErrorMessageService protected _errorMessageService: IErrorMessageService
|
||||
) {
|
||||
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
|
||||
}
|
||||
|
||||
public async run(actionContext: any): Promise<boolean> {
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
//set objectExplorerTreeNode for context menu clicks
|
||||
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
|
||||
}
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
|
||||
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
|
||||
var ownerUri = this._connectionManagementService.getConnectionUri(connectionProfile);
|
||||
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
|
||||
|
||||
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class OEScriptDeleteAction extends ScriptDeleteAction {
|
||||
public static ID = 'objectExplorer.' + ScriptDeleteAction.ID;
|
||||
private _objectExplorerTreeNode: TreeNode;
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
|
||||
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
|
||||
@IScriptingService protected _scriptingService: IScriptingService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IErrorMessageService protected _errorMessageService: IErrorMessageService
|
||||
) {
|
||||
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
|
||||
}
|
||||
|
||||
public async run(actionContext: any): Promise<boolean> {
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
if (actionContext instanceof ObjectExplorerActionsContext) {
|
||||
//set objectExplorerTreeNode for context menu clicks
|
||||
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
|
||||
}
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
|
||||
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata;
|
||||
var ownerUri = this._connectionManagementService.getConnectionUri(connectionProfile);
|
||||
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
|
||||
|
||||
return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ObjectExplorerActionUtilities {
|
||||
|
||||
public static readonly objectExplorerElementClass = 'object-element-group';
|
||||
public static readonly connectionElementClass = 'connection-tile';
|
||||
|
||||
public static getScriptMap(treeNode: TreeNode): Map<NodeType, any[]> {
|
||||
let scriptMap = new Map<NodeType, any[]>();
|
||||
|
||||
let isMssqlProvider: boolean = true;
|
||||
if (treeNode) {
|
||||
let connectionProfile = treeNode.getConnectionProfile();
|
||||
if (connectionProfile) {
|
||||
isMssqlProvider = connectionProfile.providerName === Constants.mssqlProviderName;
|
||||
}
|
||||
}
|
||||
|
||||
let basicScripting = [OEScriptCreateAction, OEScriptDeleteAction];
|
||||
let storedProcedureScripting = isMssqlProvider ? [OEScriptCreateAction, OEScriptAlterAction, OEScriptDeleteAction, OEScriptExecuteAction] :
|
||||
basicScripting;
|
||||
|
||||
let viewScripting = isMssqlProvider ? [OEScriptSelectAction, OEScriptCreateAction, OEScriptAlterAction, OEScriptDeleteAction] :
|
||||
[OEScriptSelectAction, OEScriptCreateAction, OEScriptDeleteAction];
|
||||
|
||||
let functionScripting = isMssqlProvider ? [OEScriptCreateAction, OEScriptAlterAction, OEScriptDeleteAction] :
|
||||
basicScripting;
|
||||
scriptMap.set(NodeType.AggregateFunction, functionScripting);
|
||||
scriptMap.set(NodeType.PartitionFunction, functionScripting);
|
||||
scriptMap.set(NodeType.ScalarValuedFunction, functionScripting);
|
||||
scriptMap.set(NodeType.Schema, basicScripting);
|
||||
scriptMap.set(NodeType.StoredProcedure, storedProcedureScripting);
|
||||
scriptMap.set(NodeType.Table, [OEScriptSelectAction, OEEditDataAction, OEScriptCreateAction, OEScriptDeleteAction]);
|
||||
scriptMap.set(NodeType.TableValuedFunction, functionScripting);
|
||||
scriptMap.set(NodeType.User, basicScripting);
|
||||
scriptMap.set(NodeType.UserDefinedTableType, basicScripting);
|
||||
scriptMap.set(NodeType.View, viewScripting);
|
||||
return scriptMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
|
||||
|
||||
/**
|
||||
* Implements the DataSource(that returns a parent/children of an element) for the recent connection tree
|
||||
*/
|
||||
export class RecentConnectionDataSource 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 ConnectionProfile) {
|
||||
return (<ConnectionProfile>element).id;
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>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 ConnectionProfile) {
|
||||
return false;
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return (<ConnectionProfileGroup>element).hasChildren();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's children as an array in a promise.
|
||||
*/
|
||||
public getChildren(tree: ITree, element: any): Promise<any> {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return Promise.resolve(null);
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return Promise.resolve((<ConnectionProfileGroup>element).getChildren());
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's parent in a promise.
|
||||
*/
|
||||
public getParent(tree: ITree, element: any): Promise<any> {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return Promise.resolve((<ConnectionProfile>element).getParent());
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return Promise.resolve((<ConnectionProfileGroup>element).getParent());
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/serverGroupDialog';
|
||||
|
||||
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
|
||||
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ILayoutService } from 'vs/platform/layout/browser/layoutService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { Modal } from 'sql/workbench/browser/modal/modal';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
|
||||
import { ServerGroupViewModel } from 'sql/workbench/parts/objectExplorer/common/serverGroupViewModel';
|
||||
import { attachButtonStyler, attachModalDialogStyler } from 'sql/platform/theme/common/styler';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/telemetryKeys';
|
||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||
|
||||
export class ServerGroupDialog extends Modal {
|
||||
private _addServerButton: Button;
|
||||
private _closeButton: Button;
|
||||
private _colorCheckBoxesMap: Array<{ color: string, checkbox: Checkbox }> = [];
|
||||
private _selectedColorOption: number;
|
||||
private _groupNameInputBox: InputBox;
|
||||
private _groupDescriptionInputBox: InputBox;
|
||||
private _viewModel: ServerGroupViewModel;
|
||||
private _skipGroupNameValidation: boolean = false;
|
||||
private _serverGroupContainer: HTMLElement;
|
||||
|
||||
private _onAddServerGroup = new Emitter<void>();
|
||||
public onAddServerGroup: Event<void> = this._onAddServerGroup.event;
|
||||
|
||||
private _onCancel = new Emitter<void>();
|
||||
public onCancel: Event<void> = this._onCancel.event;
|
||||
|
||||
private _onCloseEvent = new Emitter<void>();
|
||||
public onCloseEvent: Event<void> = this._onCloseEvent.event;
|
||||
|
||||
constructor(
|
||||
@ILayoutService layoutService: ILayoutService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextViewService private _contextViewService: IContextViewService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IClipboardService clipboardService: IClipboardService
|
||||
) {
|
||||
super(localize('ServerGroupsDialogTitle', 'Server Groups'), TelemetryKeys.ServerGroups, telemetryService, layoutService, clipboardService, themeService, contextKeyService);
|
||||
}
|
||||
|
||||
public render() {
|
||||
super.render();
|
||||
attachModalDialogStyler(this, this._themeService);
|
||||
const okLabel = localize('serverGroup.ok', "OK");
|
||||
const cancelLabel = localize('serverGroup.cancel', "Cancel");
|
||||
this._addServerButton = this.addFooterButton(okLabel, () => this.addGroup());
|
||||
this._closeButton = this.addFooterButton(cancelLabel, () => this.cancel());
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
protected layout(height?: number): void {
|
||||
// NO OP
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement) {
|
||||
const body = DOM.append(container, DOM.$('.server-group-dialog'));
|
||||
|
||||
// Connection Group Name
|
||||
const serverGroupNameLabel = localize('connectionGroupName', "Server group name");
|
||||
|
||||
DOM.append(body, DOM.$('.dialog-label')).innerText = serverGroupNameLabel;
|
||||
|
||||
this._groupNameInputBox = new InputBox(DOM.append(body, DOM.$('.input-divider')), this._contextViewService, {
|
||||
validationOptions: {
|
||||
validation: (value: string) => !value && !this._skipGroupNameValidation ? ({ type: MessageType.ERROR, content: localize('MissingGroupNameError', "Group name is required.") }) : null
|
||||
},
|
||||
ariaLabel: serverGroupNameLabel
|
||||
});
|
||||
|
||||
// Connection Group Description
|
||||
const groupDescriptionLabel = localize('groupDescription', "Group description");
|
||||
DOM.append(body, DOM.$('.dialog-label')).innerText = groupDescriptionLabel;
|
||||
|
||||
this._groupDescriptionInputBox = new InputBox(DOM.append(body, DOM.$('.input-divider')), this._contextViewService, {
|
||||
ariaLabel: groupDescriptionLabel
|
||||
});
|
||||
|
||||
// Connection Group Color
|
||||
const groupColorLabel = localize('groupColor', "Group color");
|
||||
DOM.append(body, DOM.$('.dialog-label')).innerText = groupColorLabel;
|
||||
|
||||
this._serverGroupContainer = DOM.append(body, DOM.$('.group-color-options'));
|
||||
this.fillGroupColors(this._serverGroupContainer);
|
||||
|
||||
DOM.addStandardDisposableListener(body, DOM.EventType.KEY_DOWN, (event: StandardKeyboardEvent) => {
|
||||
if (event.equals(KeyMod.Shift | KeyCode.Tab)) {
|
||||
this.preventDefaultKeyboardEvent(event);
|
||||
this.focusPrevious();
|
||||
} else if (event.equals(KeyCode.Tab)) {
|
||||
this.preventDefaultKeyboardEvent(event);
|
||||
this.focusNext();
|
||||
} else if (event.equals(KeyCode.RightArrow) || event.equals(KeyCode.LeftArrow)) {
|
||||
this.preventDefaultKeyboardEvent(event);
|
||||
this.focusNextColor(event.equals(KeyCode.RightArrow));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private preventDefaultKeyboardEvent(e: StandardKeyboardEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
private isFocusOnColors(): boolean {
|
||||
let result = false;
|
||||
this._colorCheckBoxesMap.forEach(({ checkbox }) => {
|
||||
if (document.activeElement === checkbox.domNode) {
|
||||
result = true;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private focusNext(): void {
|
||||
if (this._groupNameInputBox.hasFocus()) {
|
||||
this._groupDescriptionInputBox.focus();
|
||||
} else if (this._groupDescriptionInputBox.hasFocus()) {
|
||||
this._colorCheckBoxesMap[this._selectedColorOption].checkbox.focus();
|
||||
} else if (this.isFocusOnColors()) {
|
||||
this._addServerButton.enabled ? this._addServerButton.focus() : this._closeButton.focus();
|
||||
} else if (document.activeElement === this._addServerButton.element) {
|
||||
this._closeButton.focus();
|
||||
}
|
||||
else if (document.activeElement === this._closeButton.element) {
|
||||
this._groupNameInputBox.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private focusPrevious(): void {
|
||||
if (document.activeElement === this._closeButton.element) {
|
||||
this._addServerButton.enabled ? this._addServerButton.focus() : this._colorCheckBoxesMap[this._selectedColorOption].checkbox.focus();
|
||||
} else if (document.activeElement === this._addServerButton.element) {
|
||||
this._colorCheckBoxesMap[this._selectedColorOption].checkbox.focus();
|
||||
} else if (this.isFocusOnColors()) {
|
||||
this._groupDescriptionInputBox.focus();
|
||||
} else if (this._groupDescriptionInputBox.hasFocus()) {
|
||||
this._groupNameInputBox.focus();
|
||||
} else if (this._groupNameInputBox.hasFocus()) {
|
||||
this._closeButton.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private focusNextColor(moveRight: boolean): void {
|
||||
let focusIndex: number = -1;
|
||||
for (let i = 0; i < this._colorCheckBoxesMap.length; i++) {
|
||||
if (document.activeElement === this._colorCheckBoxesMap[i].checkbox.domNode) {
|
||||
focusIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (focusIndex >= 0) {
|
||||
if (moveRight) {
|
||||
focusIndex++;
|
||||
}
|
||||
else {
|
||||
focusIndex--;
|
||||
}
|
||||
|
||||
// check for wraps
|
||||
if (focusIndex < 0) {
|
||||
focusIndex = this._colorCheckBoxesMap.length - 1;
|
||||
} else if (focusIndex >= this._colorCheckBoxesMap.length) {
|
||||
focusIndex = 0;
|
||||
}
|
||||
|
||||
this._colorCheckBoxesMap[focusIndex].checkbox.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private onSelectGroupColor(colorToSelect: string): void {
|
||||
this._viewModel.groupColor = colorToSelect;
|
||||
this._selectedColorOption = this._viewModel.colors.indexOf(colorToSelect);
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
// Theme styler
|
||||
this._register(attachInputBoxStyler(this._groupNameInputBox, this._themeService));
|
||||
this._register(attachInputBoxStyler(this._groupDescriptionInputBox, this._themeService));
|
||||
this._register(attachButtonStyler(this._addServerButton, this._themeService));
|
||||
this._register(attachButtonStyler(this._closeButton, this._themeService));
|
||||
|
||||
// handler for name change events
|
||||
this._register(this._groupNameInputBox.onDidChange(groupName => {
|
||||
this.groupNameChanged(groupName);
|
||||
}));
|
||||
|
||||
// handler for description change events
|
||||
this._register(this._groupDescriptionInputBox.onDidChange(groupDescription => {
|
||||
this.groupDescriptionChanged(groupDescription);
|
||||
}));
|
||||
}
|
||||
|
||||
private fillGroupColors(container: HTMLElement): void {
|
||||
for (let i = 0; i < this._viewModel.colors.length; i++) {
|
||||
const color = this._viewModel.colors[i];
|
||||
|
||||
const colorCheckBox = new Checkbox({
|
||||
actionClassName: 'server-group-color',
|
||||
title: color,
|
||||
isChecked: false
|
||||
});
|
||||
this._register(colorCheckBox.onChange((viaKeyboard) => {
|
||||
this.onSelectGroupColor(color);
|
||||
}));
|
||||
colorCheckBox.domNode.style.backgroundColor = color;
|
||||
container.appendChild(colorCheckBox.domNode);
|
||||
|
||||
// Theme styler
|
||||
this._register(attachCheckboxStyler(colorCheckBox, this._themeService));
|
||||
|
||||
// add the new checkbox to the color map
|
||||
this._colorCheckBoxesMap[i] = { color, checkbox: colorCheckBox };
|
||||
}
|
||||
}
|
||||
|
||||
private groupNameChanged(groupName: string) {
|
||||
this._viewModel.groupName = groupName;
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
private groupDescriptionChanged(groupDescription: string) {
|
||||
this._viewModel.groupDescription = groupDescription;
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
public get groupName(): string {
|
||||
return this._groupNameInputBox.value;
|
||||
}
|
||||
|
||||
public get groupDescription(): string {
|
||||
return this._groupDescriptionInputBox.value;
|
||||
}
|
||||
|
||||
public get selectedColor(): string {
|
||||
return this._colorCheckBoxesMap[this._selectedColorOption].color;
|
||||
}
|
||||
|
||||
public get viewModel(): ServerGroupViewModel {
|
||||
return this._viewModel;
|
||||
}
|
||||
public set viewModel(theViewModel: ServerGroupViewModel) {
|
||||
this._viewModel = theViewModel;
|
||||
if (this._serverGroupContainer) {
|
||||
DOM.clearNode(this._serverGroupContainer);
|
||||
this.fillGroupColors(this._serverGroupContainer);
|
||||
}
|
||||
}
|
||||
|
||||
public addGroup(): void {
|
||||
if (this._addServerButton.enabled) {
|
||||
if (this.validateInputs()) {
|
||||
this._onAddServerGroup.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public hideError() {
|
||||
this.setError('');
|
||||
}
|
||||
|
||||
private validateInputs(): boolean {
|
||||
let validate = this._groupNameInputBox.validate();
|
||||
if (!validate) {
|
||||
this._groupNameInputBox.focus();
|
||||
}
|
||||
return validate;
|
||||
}
|
||||
|
||||
// initialize the view based on the current state of the view model
|
||||
private initializeView(): void {
|
||||
this.title = this._viewModel.getDialogTitle();
|
||||
|
||||
this._skipGroupNameValidation = true;
|
||||
this._groupNameInputBox.value = this._viewModel.groupName;
|
||||
this._skipGroupNameValidation = false;
|
||||
|
||||
this._groupDescriptionInputBox.value = this._viewModel.groupDescription;
|
||||
|
||||
this.updateView();
|
||||
}
|
||||
|
||||
// update UI elements that have derivative behaviors based on other state changes
|
||||
private updateView(): void {
|
||||
// check the color buttons and if their checked state does not match the view model state then correct it
|
||||
for (let i = 0; i < this._colorCheckBoxesMap.length; i++) {
|
||||
let { checkbox, color } = this._colorCheckBoxesMap[i];
|
||||
if ((this._viewModel.groupColor === color) && (checkbox.checked === false)) {
|
||||
checkbox.checked = true;
|
||||
this._selectedColorOption = i;
|
||||
} else if ((this._viewModel.groupColor !== color) && (checkbox.checked === true)) {
|
||||
checkbox.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// OK button state - enabled if there are pending changes that can be saved
|
||||
this._addServerButton.enabled = this._viewModel.hasPendingChanges();
|
||||
}
|
||||
|
||||
/* Overwrite escape key behavior */
|
||||
protected onClose() {
|
||||
this.cancel();
|
||||
}
|
||||
|
||||
/* Overwrite enter key behavior */
|
||||
protected onAccept() {
|
||||
this.addGroup();
|
||||
}
|
||||
|
||||
public cancel() {
|
||||
this._onCancel.fire();
|
||||
this.close();
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.hide();
|
||||
this._groupNameInputBox.hideMessage();
|
||||
this._onCloseEvent.fire();
|
||||
}
|
||||
|
||||
public open() {
|
||||
// reset the dialog
|
||||
this.hideError();
|
||||
this.initializeView();
|
||||
this.show();
|
||||
this._groupNameInputBox.focus();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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 { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
|
||||
import {
|
||||
DisconnectConnectionAction, AddServerAction,
|
||||
DeleteConnectionAction, RefreshAction, EditServerGroupAction
|
||||
} from 'sql/workbench/parts/objectExplorer/browser/connectionTreeAction';
|
||||
import {
|
||||
ObjectExplorerActionUtilities, ManageConnectionAction, OEAction
|
||||
} from 'sql/workbench/parts/objectExplorer/browser/objectExplorerActions';
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import { NodeType } from 'sql/workbench/parts/objectExplorer/common/nodeType';
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { MenuId, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { NewQueryAction, BackupAction, RestoreAction } from 'sql/workbench/common/actions';
|
||||
import { ConnectionContextKey } from 'sql/workbench/parts/connection/common/connectionContextKey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { TreeNodeContextKey } from 'sql/workbench/parts/objectExplorer/common/treeNodeContextKey';
|
||||
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
|
||||
import { IScriptingService } from 'sql/platform/scripting/common/scriptingService';
|
||||
import { ServerInfoContextKey } from 'sql/workbench/parts/connection/common/serverInfoContextKey';
|
||||
|
||||
/**
|
||||
* Provides actions for the server tree elements
|
||||
*/
|
||||
export class ServerTreeActionProvider extends ContributableActionProvider {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IQueryManagementService private _queryManagementService: IQueryManagementService,
|
||||
@IScriptingService private _scriptingService: IScriptingService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IContextKeyService private _contextKeyService: IContextKeyService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public hasActions(tree: ITree, element: any): boolean {
|
||||
return element instanceof ConnectionProfileGroup || (element instanceof ConnectionProfile) || (element instanceof TreeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions given an element in the tree
|
||||
*/
|
||||
public getActions(tree: ITree, element: any): IAction[] {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return this.getConnectionActions(tree, element);
|
||||
}
|
||||
if (element instanceof ConnectionProfileGroup) {
|
||||
return this.getConnectionProfileGroupActions(tree, element);
|
||||
}
|
||||
if (element instanceof TreeNode) {
|
||||
return this.getObjectExplorerNodeActions({
|
||||
tree: tree,
|
||||
profile: element.getConnectionProfile(),
|
||||
treeNode: element
|
||||
});
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions for connection elements
|
||||
*/
|
||||
public getConnectionActions(tree: ITree, profile: ConnectionProfile): IAction[] {
|
||||
let node = new TreeNode(NodeType.Server, '', false, '', '', '', undefined, undefined, undefined, undefined);
|
||||
return this.getAllActions({
|
||||
tree: tree,
|
||||
profile: profile,
|
||||
treeNode: node
|
||||
}, (context) => this.getBuiltinConnectionActions(context));
|
||||
}
|
||||
|
||||
private getAllActions(context: ObjectExplorerContext, getDefaultActions: (ObjectExplorerContext) => IAction[]) {
|
||||
// Create metadata needed to get a useful set of actions
|
||||
let scopedContextService = this.getContextKeyService(context);
|
||||
let menu = this.menuService.createMenu(MenuId.ObjectExplorerItemContext, scopedContextService);
|
||||
|
||||
// Fill in all actions
|
||||
let actions = getDefaultActions(context);
|
||||
let options = { arg: undefined, shouldForwardArgs: true };
|
||||
const groups = menu.getActions(options);
|
||||
fillInActions(groups, actions, false);
|
||||
|
||||
// Cleanup
|
||||
scopedContextService.dispose();
|
||||
menu.dispose();
|
||||
return actions;
|
||||
|
||||
}
|
||||
|
||||
private getBuiltinConnectionActions(context: ObjectExplorerContext): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL, context.tree));
|
||||
this.addNewQueryAction(context, actions);
|
||||
|
||||
if (this._connectionManagementService.isProfileConnected(context.profile)) {
|
||||
actions.push(this._instantiationService.createInstance(DisconnectConnectionAction, DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL, context.profile));
|
||||
}
|
||||
actions.push(this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, context.profile));
|
||||
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, context.profile));
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private getContextKeyService(context: ObjectExplorerContext): IContextKeyService {
|
||||
let scopedContextService = this._contextKeyService.createScoped();
|
||||
let connectionContextKey = new ConnectionContextKey(scopedContextService);
|
||||
let connectionProfile = context && context.profile;
|
||||
connectionContextKey.set(connectionProfile);
|
||||
let serverInfoContextKey = new ServerInfoContextKey(scopedContextService);
|
||||
if (connectionProfile.id) {
|
||||
let serverInfo = this._connectionManagementService.getServerInfo(connectionProfile.id);
|
||||
serverInfoContextKey.set(serverInfo);
|
||||
}
|
||||
let treeNodeContextKey = new TreeNodeContextKey(scopedContextService);
|
||||
if (context.treeNode) {
|
||||
treeNodeContextKey.set(context.treeNode);
|
||||
}
|
||||
return scopedContextService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions for connection group elements
|
||||
*/
|
||||
public getConnectionProfileGroupActions(tree: ITree, element: ConnectionProfileGroup): IAction[] {
|
||||
return [
|
||||
this._instantiationService.createInstance(AddServerAction, AddServerAction.ID, AddServerAction.LABEL),
|
||||
this._instantiationService.createInstance(EditServerGroupAction, EditServerGroupAction.ID, EditServerGroupAction.LABEL, element),
|
||||
this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_GROUP_LABEL, element)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return actions for OE elements
|
||||
*/
|
||||
private getObjectExplorerNodeActions(context: ObjectExplorerContext): IAction[] {
|
||||
return this.getAllActions(context, (context) => this.getBuiltInNodeActions(context));
|
||||
}
|
||||
|
||||
private getBuiltInNodeActions(context: ObjectExplorerContext): IAction[] {
|
||||
let actions: IAction[] = [];
|
||||
let treeNode = context.treeNode;
|
||||
let isAvailableDatabaseNode = false;
|
||||
if (TreeUpdateUtils.isDatabaseNode(treeNode)) {
|
||||
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
|
||||
isAvailableDatabaseNode = true;
|
||||
actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL, context.tree));
|
||||
this.addNewQueryAction(context, actions);
|
||||
} else {
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
this.addScriptingActions(context, actions);
|
||||
|
||||
let serverInfo = this._connectionManagementService.getServerInfo(context.profile.id);
|
||||
let isCloud = serverInfo && serverInfo.isCloud;
|
||||
|
||||
if (isAvailableDatabaseNode && !isCloud) {
|
||||
this.addBackupAction(context, actions);
|
||||
this.addRestoreAction(context, actions);
|
||||
}
|
||||
|
||||
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, treeNode));
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private addNewQueryAction(context: ObjectExplorerContext, actions: IAction[]): void {
|
||||
if (this._queryManagementService.isProviderRegistered(context.profile.providerName)) {
|
||||
actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL));
|
||||
}
|
||||
}
|
||||
|
||||
private addBackupAction(context: ObjectExplorerContext, actions: IAction[]): void {
|
||||
if (this._queryManagementService.isProviderRegistered(context.profile.providerName)) {
|
||||
actions.push(this._instantiationService.createInstance(OEAction, BackupAction.ID, BackupAction.LABEL));
|
||||
}
|
||||
}
|
||||
|
||||
private addRestoreAction(context: ObjectExplorerContext, actions: IAction[]): void {
|
||||
if (this._queryManagementService.isProviderRegistered(context.profile.providerName)) {
|
||||
actions.push(this._instantiationService.createInstance(OEAction, RestoreAction.ID, RestoreAction.LABEL));
|
||||
}
|
||||
}
|
||||
|
||||
private addScriptingActions(context: ObjectExplorerContext, actions: IAction[]): void {
|
||||
if (this._scriptingService.isProviderRegistered(context.profile.providerName)) {
|
||||
let scriptMap: Map<NodeType, any[]> = ObjectExplorerActionUtilities.getScriptMap(context.treeNode);
|
||||
let supportedActions = scriptMap.get(context.treeNode.nodeTypeId);
|
||||
let self = this;
|
||||
if (supportedActions !== null && supportedActions !== undefined) {
|
||||
supportedActions.forEach(action => {
|
||||
actions.push(self._instantiationService.createInstance(action, action.ID, action.LABEL));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ObjectExplorerContext {
|
||||
tree: ITree;
|
||||
profile: ConnectionProfile;
|
||||
treeNode?: TreeNode;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITree, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
import * as treedefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
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 { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ServerTreeActionProvider } from 'sql/workbench/parts/objectExplorer/browser/serverTreeActionProvider';
|
||||
import { ObjectExplorerActionsContext } from 'sql/workbench/parts/objectExplorer/browser/objectExplorerActions';
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
|
||||
/**
|
||||
* Extends the tree controller to handle clicks on the tree elements
|
||||
*/
|
||||
export class ServerTreeController extends treedefaults.DefaultController {
|
||||
|
||||
constructor(
|
||||
private actionProvider: ServerTreeActionProvider,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService
|
||||
) {
|
||||
super({
|
||||
clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN,
|
||||
openMode: treedefaults.OpenMode.SINGLE_CLICK
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
var actionContext: any;
|
||||
if (element instanceof TreeNode) {
|
||||
let context = new ObjectExplorerActionsContext();
|
||||
context.nodeInfo = element.toNodeInfo();
|
||||
// Note: getting DB name before, but intentionally not using treeUpdateUtils.getConnectionProfile as it replaces
|
||||
// the connection ID with a new one. This breaks a number of internal tasks
|
||||
context.connectionProfile = element.getConnectionProfile().toIConnectionProfile();
|
||||
context.connectionProfile.databaseName = element.getDatabaseName();
|
||||
actionContext = context;
|
||||
} else if (element instanceof ConnectionProfile) {
|
||||
let context = new ObjectExplorerActionsContext();
|
||||
context.connectionProfile = element.toIConnectionProfile();
|
||||
context.isConnectionNode = true;
|
||||
actionContext = context;
|
||||
} else {
|
||||
// TODO: because the connection group is used as a context object and isn't serializable,
|
||||
// the Group-level context menu is not currently extensible
|
||||
actionContext = 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: () => (actionContext)
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
|
||||
import { TreeNode, TreeItemCollapsibleState } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
|
||||
/**
|
||||
* Implements the DataSource(that returns a parent/children of an element) for the server tree
|
||||
*/
|
||||
export class ServerTreeDataSource implements IDataSource {
|
||||
|
||||
constructor(
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ConnectionProfile
|
||||
|| element instanceof ConnectionProfileGroup
|
||||
|| element instanceof TreeNode) {
|
||||
return 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 ConnectionProfile) {
|
||||
return true;
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return element.hasChildren();
|
||||
} else if (element instanceof TreeNode) {
|
||||
return !element.isAlwaysLeaf;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's children as an array in a promise.
|
||||
*/
|
||||
public getChildren(tree: ITree, element: any): Promise<any> {
|
||||
return new Promise<any>((resolve) => {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
TreeUpdateUtils.getObjectExplorerNode(<ConnectionProfile>element, this._connectionManagementService, this._objectExplorerService).then(nodes => {
|
||||
resolve(nodes);
|
||||
}, error => {
|
||||
resolve([]);
|
||||
});
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
resolve((<ConnectionProfileGroup>element).getChildren());
|
||||
} else if (element instanceof TreeNode) {
|
||||
var node = element;
|
||||
if (node.children) {
|
||||
resolve(node.children);
|
||||
} else {
|
||||
// These similar changes are probably needed for a ConnectionProfile group element as well. However, we do not have a repro of a failiure in that scenario so they will be tackled in a future checkin.
|
||||
// It has been tested for connecting to the server in profile itself and things work fine there.
|
||||
this._objectExplorerService.resolveTreeNodeChildren(node.getSession(), node).then(() => {
|
||||
resolve(node.children);
|
||||
}, expandError => {
|
||||
node.setExpandedState(TreeItemCollapsibleState.Collapsed);
|
||||
node.errorStateMessage = expandError;
|
||||
this.showError(expandError);
|
||||
// collapse node and refresh in case of error so remove tree cache
|
||||
setTimeout(() => {
|
||||
tree.collapse(element).then(() => tree.refresh(element));
|
||||
});
|
||||
resolve([]);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's parent in a promise.
|
||||
*/
|
||||
public getParent(tree: ITree, element: any): Promise<any> {
|
||||
if (element instanceof ConnectionProfile) {
|
||||
return Promise.resolve(element.getParent());
|
||||
} else if (element instanceof ConnectionProfileGroup) {
|
||||
return Promise.resolve(element.getParent());
|
||||
} else if (element instanceof TreeNode) {
|
||||
return Promise.resolve(TreeUpdateUtils.getObjectExplorerParent(element, this._connectionManagementService));
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
private showError(errorMessage: string) {
|
||||
if (this._errorMessageService) {
|
||||
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/media/objectTypes/objecttypes';
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
|
||||
export interface IConnectionTemplateData {
|
||||
root: HTMLElement;
|
||||
label: HTMLSpanElement;
|
||||
icon: HTMLElement;
|
||||
connectionProfile: ConnectionProfile;
|
||||
}
|
||||
|
||||
export interface IConnectionProfileGroupTemplateData {
|
||||
root: HTMLElement;
|
||||
name: HTMLSpanElement;
|
||||
inputBox: InputBox;
|
||||
}
|
||||
|
||||
export interface IObjectExplorerTemplateData {
|
||||
root: HTMLElement;
|
||||
label: HTMLSpanElement;
|
||||
icon: HTMLElement;
|
||||
treeNode: TreeNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the tree items.
|
||||
* Uses the dom template to render connection groups and connections.
|
||||
*/
|
||||
export class ServerTreeRenderer implements IRenderer {
|
||||
|
||||
public static CONNECTION_HEIGHT = 25;
|
||||
public static CONNECTION_GROUP_HEIGHT = 38;
|
||||
private static CONNECTION_TEMPLATE_ID = 'connectionProfile';
|
||||
private static CONNECTION_GROUP_TEMPLATE_ID = 'connectionProfileGroup';
|
||||
public static OBJECTEXPLORER_HEIGHT = 25;
|
||||
private static OBJECTEXPLORER_TEMPLATE_ID = 'objectExplorer';
|
||||
/**
|
||||
* _isCompact is used to render connections tiles with and without the action buttons.
|
||||
* When set to true, like in the connection dialog recent connections tree, the connection
|
||||
* tile is rendered without the action buttons( such as connect, new query).
|
||||
*/
|
||||
private _isCompact: boolean = false;
|
||||
|
||||
constructor(
|
||||
isCompact: boolean,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
|
||||
) {
|
||||
// isCompact defaults to false unless explicitly set by instantiation call.
|
||||
if (isCompact) {
|
||||
this._isCompact = isCompact;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's height in the tree, in pixels.
|
||||
*/
|
||||
public getHeight(tree: ITree, element: any): number {
|
||||
if (element instanceof ConnectionProfileGroup) {
|
||||
return ServerTreeRenderer.CONNECTION_GROUP_HEIGHT;
|
||||
} else if (element instanceof ConnectionProfile) {
|
||||
return ServerTreeRenderer.CONNECTION_HEIGHT;
|
||||
}
|
||||
return ServerTreeRenderer.OBJECTEXPLORER_HEIGHT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a template ID for a given element.
|
||||
*/
|
||||
public getTemplateId(tree: ITree, element: any): string {
|
||||
if (element instanceof ConnectionProfileGroup) {
|
||||
return ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID;
|
||||
} else if (element instanceof ConnectionProfile) {
|
||||
return ServerTreeRenderer.CONNECTION_TEMPLATE_ID;
|
||||
}
|
||||
return ServerTreeRenderer.OBJECTEXPLORER_TEMPLATE_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render template in a dom element based on template id
|
||||
*/
|
||||
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
|
||||
|
||||
if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) {
|
||||
const connectionTemplate: IObjectExplorerTemplateData = Object.create(null);
|
||||
connectionTemplate.root = dom.append(container, dom.$('.connection-tile'));
|
||||
connectionTemplate.icon = dom.append(connectionTemplate.root, dom.$('div.icon server-page'));
|
||||
connectionTemplate.label = dom.append(connectionTemplate.root, dom.$('div.label'));
|
||||
return connectionTemplate;
|
||||
} else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) {
|
||||
container.classList.add('server-group');
|
||||
const groupTemplate: IConnectionProfileGroupTemplateData = Object.create(null);
|
||||
groupTemplate.root = dom.append(container, dom.$('.server-group'));
|
||||
groupTemplate.name = dom.append(groupTemplate.root, dom.$('span.name'));
|
||||
return groupTemplate;
|
||||
} else {
|
||||
const objectExplorerTemplate: IObjectExplorerTemplateData = Object.create(null);
|
||||
objectExplorerTemplate.root = dom.append(container, dom.$('.object-element-group'));
|
||||
objectExplorerTemplate.icon = dom.append(objectExplorerTemplate.root, dom.$('div.object-icon'));
|
||||
objectExplorerTemplate.label = dom.append(objectExplorerTemplate.root, dom.$('div.label'));
|
||||
return objectExplorerTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a element, given an object bag returned by the template
|
||||
*/
|
||||
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
|
||||
if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) {
|
||||
this.renderConnection(element, templateData);
|
||||
} else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) {
|
||||
this.renderConnectionProfileGroup(element, templateData);
|
||||
} else {
|
||||
this.renderObjectExplorer(element, templateData);
|
||||
}
|
||||
}
|
||||
|
||||
private renderObjectExplorer(treeNode: TreeNode, templateData: IObjectExplorerTemplateData): void {
|
||||
// Use an explicitly defined iconType first. If not defined, fall back to using nodeType and
|
||||
// other compount indicators instead.
|
||||
let iconName: string = undefined;
|
||||
if (treeNode.iconType) {
|
||||
iconName = (typeof treeNode.iconType === 'string') ? treeNode.iconType : treeNode.iconType.id;
|
||||
} else {
|
||||
iconName = treeNode.nodeTypeId;
|
||||
if (treeNode.nodeStatus) {
|
||||
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeStatus;
|
||||
}
|
||||
if (treeNode.nodeSubType) {
|
||||
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeSubType;
|
||||
}
|
||||
}
|
||||
|
||||
let tokens: string[] = [];
|
||||
for (let index = 1; index < templateData.icon.classList.length; index++) {
|
||||
tokens.push(templateData.icon.classList.item(index));
|
||||
}
|
||||
templateData.icon.classList.remove(...tokens);
|
||||
templateData.icon.classList.add('icon');
|
||||
let iconLowerCaseName = iconName.toLocaleLowerCase();
|
||||
templateData.icon.classList.add(iconLowerCaseName);
|
||||
|
||||
templateData.label.textContent = treeNode.label;
|
||||
templateData.root.title = treeNode.label;
|
||||
}
|
||||
|
||||
|
||||
private renderConnection(connection: ConnectionProfile, templateData: IConnectionTemplateData): void {
|
||||
if (!this._isCompact) {
|
||||
if (this._connectionManagementService.isConnected(undefined, connection)) {
|
||||
templateData.icon.classList.remove('disconnected');
|
||||
templateData.icon.classList.add('connected');
|
||||
} else {
|
||||
templateData.icon.classList.remove('connected');
|
||||
templateData.icon.classList.add('disconnected');
|
||||
}
|
||||
}
|
||||
|
||||
let label = connection.title;
|
||||
if (!connection.isConnectionOptionsValid) {
|
||||
label = localize('loading', 'Loading...');
|
||||
}
|
||||
|
||||
templateData.label.textContent = label;
|
||||
templateData.root.title = connection.serverInfo;
|
||||
templateData.connectionProfile = connection;
|
||||
}
|
||||
|
||||
private renderConnectionProfileGroup(connectionProfileGroup: ConnectionProfileGroup, templateData: IConnectionProfileGroupTemplateData): void {
|
||||
|
||||
let rowElement = this.findParentElement(templateData.root, 'monaco-tree-row');
|
||||
if (rowElement) {
|
||||
if (connectionProfileGroup.color) {
|
||||
rowElement.style.background = connectionProfileGroup.color;
|
||||
} else {
|
||||
// If the group doesn't contain specific color, assign the default color
|
||||
rowElement.style.background = '#515151';
|
||||
}
|
||||
}
|
||||
if (connectionProfileGroup.description && (connectionProfileGroup.description !== '')) {
|
||||
templateData.root.title = connectionProfileGroup.description;
|
||||
}
|
||||
templateData.name.hidden = false;
|
||||
templateData.name.textContent = connectionProfileGroup.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first parent which contains the className
|
||||
*/
|
||||
private findParentElement(container: HTMLElement, className: string): HTMLElement {
|
||||
let currentElement = container;
|
||||
while (currentElement) {
|
||||
if (currentElement.className.includes(className)) {
|
||||
break;
|
||||
}
|
||||
currentElement = currentElement.parentElement;
|
||||
}
|
||||
return currentElement;
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
|
||||
// no op
|
||||
// InputBox disposed in wrapUp
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
522
src/sql/workbench/parts/objectExplorer/browser/serverTreeView.ts
Normal file
@@ -0,0 +1,522 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/serverTreeActions';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
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 { localize } from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { append, $, hide, show } from 'vs/base/browser/dom';
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import * as ConnectionUtils from 'sql/platform/connection/common/utils';
|
||||
import { ActiveConnectionsFilterAction } from 'sql/workbench/parts/objectExplorer/browser/connectionTreeAction';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { TreeCreationUtils } from 'sql/workbench/parts/objectExplorer/browser/treeCreationUtils';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
import { TreeSelectionHandler } from 'sql/workbench/parts/objectExplorer/browser/treeSelectionHandler';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import { attachButtonStyler } from 'sql/platform/theme/common/styler';
|
||||
import { TreeNode, TreeItemCollapsibleState } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import { SERVER_GROUP_CONFIG, SERVER_GROUP_AUTOEXPAND_CONFIG } from 'sql/workbench/parts/objectExplorer/common/serverGroup.contribution';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
import { ServerTreeActionProvider } from 'sql/workbench/parts/objectExplorer/browser/serverTreeActionProvider';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { isHidden } from 'sql/base/browser/dom';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
|
||||
/**
|
||||
* ServerTreeview implements the dynamic tree view.
|
||||
*/
|
||||
export class ServerTreeView {
|
||||
|
||||
public messages: HTMLElement;
|
||||
private _buttonSection: HTMLElement;
|
||||
private _treeSelectionHandler: TreeSelectionHandler;
|
||||
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
|
||||
private _tree: ITree;
|
||||
private _toDispose: IDisposable[] = [];
|
||||
private _onSelectionOrFocusChange: Emitter<void>;
|
||||
private _actionProvider: ServerTreeActionProvider;
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IThemeService private _themeService: IThemeService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@ICapabilitiesService capabilitiesService: ICapabilitiesService
|
||||
) {
|
||||
this._activeConnectionsFilterAction = this._instantiationService.createInstance(
|
||||
ActiveConnectionsFilterAction,
|
||||
ActiveConnectionsFilterAction.ID,
|
||||
ActiveConnectionsFilterAction.LABEL,
|
||||
this);
|
||||
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
|
||||
this._onSelectionOrFocusChange = new Emitter();
|
||||
this._actionProvider = this._instantiationService.createInstance(ServerTreeActionProvider);
|
||||
capabilitiesService.onCapabilitiesRegistered(() => {
|
||||
if (this._connectionManagementService.hasRegisteredServers()) {
|
||||
this.refreshTree();
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
}
|
||||
});
|
||||
this.registerCommands();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active connections filter action
|
||||
*/
|
||||
public get activeConnectionsFilterAction(): ActiveConnectionsFilterAction {
|
||||
return this._activeConnectionsFilterAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired when the tree's selection or focus changes
|
||||
*/
|
||||
public get onSelectionOrFocusChange(): Event<void> {
|
||||
return this._onSelectionOrFocusChange.event;
|
||||
}
|
||||
|
||||
public get treeActionProvider(): ServerTreeActionProvider {
|
||||
return this._actionProvider;
|
||||
}
|
||||
|
||||
public get tree(): ITree {
|
||||
return this._tree;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Register search related commands
|
||||
*/
|
||||
public registerCommands(): void {
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'registeredServers.searchServer',
|
||||
handler: (accessor: ServicesAccessor, ...args: any[]) => {
|
||||
this.searchTree(args[0]);
|
||||
}
|
||||
});
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'registeredServers.clearSearchServerResult',
|
||||
handler: (accessor: ServicesAccessor, ...args: any[]) => {
|
||||
this.refreshTree();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the view body
|
||||
*/
|
||||
public renderBody(container: HTMLElement): Thenable<void> {
|
||||
// Add div to display no connections found message and hide it by default
|
||||
this.messages = append(container, $('.title'));
|
||||
const messageText = append(this.messages, $('span'));
|
||||
messageText.style.paddingLeft = '10px';
|
||||
messageText.innerText = localize('servers.noConnections', "No connections found.");
|
||||
hide(this.messages);
|
||||
|
||||
if (!this._connectionManagementService.hasRegisteredServers()) {
|
||||
this._activeConnectionsFilterAction.enabled = false;
|
||||
this._buttonSection = append(container, $('.button-section'));
|
||||
const connectButton = new Button(this._buttonSection);
|
||||
connectButton.label = localize('serverTree.addConnection', "Add Connection");
|
||||
this._toDispose.push(attachButtonStyler(connectButton, this._themeService));
|
||||
this._toDispose.push(connectButton.onDidClick(() => {
|
||||
this._connectionManagementService.showConnectionDialog();
|
||||
}));
|
||||
}
|
||||
this._tree = TreeCreationUtils.createRegisteredServersTree(container, this._instantiationService);
|
||||
//this._tree.setInput(undefined);
|
||||
this._toDispose.push(this._tree.onDidChangeSelection((event) => this.onSelected(event)));
|
||||
this._toDispose.push(this._tree.onDidBlur(() => this._onSelectionOrFocusChange.fire()));
|
||||
this._toDispose.push(this._tree.onDidChangeFocus(() => this._onSelectionOrFocusChange.fire()));
|
||||
|
||||
// Theme styler
|
||||
this._toDispose.push(attachListStyler(this._tree, this._themeService));
|
||||
|
||||
// Refresh Tree when these events are emitted
|
||||
this._toDispose.push(this._connectionManagementService.onAddConnectionProfile((newProfile: IConnectionProfile) => {
|
||||
this.handleAddConnectionProfile(newProfile);
|
||||
}));
|
||||
this._toDispose.push(this._connectionManagementService.onDeleteConnectionProfile(() => {
|
||||
this.refreshTree();
|
||||
}));
|
||||
this._toDispose.push(this._connectionManagementService.onDisconnect((connectionParams) => {
|
||||
if (this.isObjectExplorerConnectionUri(connectionParams.connectionUri)) {
|
||||
this.deleteObjectExplorerNodeAndRefreshTree(connectionParams.connectionProfile);
|
||||
}
|
||||
}));
|
||||
|
||||
if (this._objectExplorerService && this._objectExplorerService.onUpdateObjectExplorerNodes) {
|
||||
this._toDispose.push(this._objectExplorerService.onUpdateObjectExplorerNodes(args => {
|
||||
if (args.errorMessage) {
|
||||
this.showError(args.errorMessage);
|
||||
}
|
||||
if (args.connection) {
|
||||
this.onObjectExplorerSessionCreated(args.connection);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
this.refreshTree();
|
||||
const root = <ConnectionProfileGroup>this._tree.getInput();
|
||||
|
||||
const expandGroups: boolean = this._configurationService.getValue(SERVER_GROUP_CONFIG)[SERVER_GROUP_AUTOEXPAND_CONFIG];
|
||||
if (expandGroups) {
|
||||
this._tree.expandAll(ConnectionProfileGroup.getSubgroups(root));
|
||||
}
|
||||
|
||||
if (root && !root.hasValidConnections) {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(true);
|
||||
resolve();
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public isObjectExplorerConnectionUri(uri: string): boolean {
|
||||
let isBackupRestoreUri: boolean = uri.indexOf(ConnectionUtils.ConnectionUriBackupIdAttributeName) >= 0 ||
|
||||
uri.indexOf(ConnectionUtils.ConnectionUriRestoreIdAttributeName) >= 0;
|
||||
return uri && uri.startsWith(ConnectionUtils.uriPrefixes.default) && !isBackupRestoreUri;
|
||||
}
|
||||
|
||||
private async handleAddConnectionProfile(newProfile: IConnectionProfile): Promise<void> {
|
||||
if (newProfile) {
|
||||
const groups = this._connectionManagementService.getConnectionGroups();
|
||||
const profile = ConnectionUtils.findProfileInGroup(newProfile, groups);
|
||||
if (profile) {
|
||||
newProfile = profile;
|
||||
}
|
||||
}
|
||||
|
||||
if (this._buttonSection) {
|
||||
hide(this._buttonSection);
|
||||
this._activeConnectionsFilterAction.enabled = true;
|
||||
}
|
||||
const currentSelections = this._tree.getSelection();
|
||||
const currentSelectedElement = currentSelections && currentSelections.length >= 1 ? currentSelections[0] : undefined;
|
||||
const newProfileIsSelected = currentSelectedElement && newProfile ? currentSelectedElement.id === newProfile.id : false;
|
||||
if (newProfile && currentSelectedElement && !newProfileIsSelected) {
|
||||
this._tree.clearSelection();
|
||||
}
|
||||
await this.refreshTree();
|
||||
if (newProfile && !newProfileIsSelected) {
|
||||
this._tree.reveal(newProfile);
|
||||
this._tree.select(newProfile);
|
||||
}
|
||||
}
|
||||
|
||||
private showError(errorMessage: string) {
|
||||
if (this._errorMessageService) {
|
||||
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private getConnectionInTreeInput(connectionId: string): ConnectionProfile {
|
||||
const root = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
|
||||
const connections = ConnectionProfileGroup.getConnectionsInGroup(root);
|
||||
const results = connections.filter(con => {
|
||||
if (connectionId === con.id) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (results && results.length > 0) {
|
||||
return results[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private onObjectExplorerSessionCreated(connection: IConnectionProfile) {
|
||||
const conn = this.getConnectionInTreeInput(connection.id);
|
||||
if (conn) {
|
||||
this._tree.refresh(conn).then(() => {
|
||||
return this._tree.expand(conn).then(() => {
|
||||
return this._tree.reveal(conn, 0.5).then(() => {
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
});
|
||||
});
|
||||
}).then(null, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
|
||||
public addObjectExplorerNodeAndRefreshTree(connection: IConnectionProfile): void {
|
||||
hide(this.messages);
|
||||
if (!this._objectExplorerService.getObjectExplorerNode(connection)) {
|
||||
this._objectExplorerService.updateObjectExplorerNodes(connection).then(() => {
|
||||
// The oe request is sent. an event will be raised when the session is created
|
||||
}, error => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public deleteObjectExplorerNodeAndRefreshTree(connection: IConnectionProfile): Thenable<void> {
|
||||
if (connection) {
|
||||
const conn = this.getConnectionInTreeInput(connection.id);
|
||||
if (conn) {
|
||||
return this._objectExplorerService.deleteObjectExplorerNode(conn).then(() => {
|
||||
this._tree.collapse(conn);
|
||||
this._tree.refresh(conn);
|
||||
});
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public refreshTree(): Promise<void> {
|
||||
hide(this.messages);
|
||||
this.clearOtherActions();
|
||||
return TreeUpdateUtils.registeredServerUpdate(this._tree, this._connectionManagementService);
|
||||
}
|
||||
|
||||
public refreshElement(element: any): Thenable<void> {
|
||||
return this._tree.refresh(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter connections based on view (recent/active)
|
||||
*/
|
||||
private filterConnections(treeInput: ConnectionProfileGroup[], view: string): ConnectionProfileGroup[] {
|
||||
if (!treeInput || treeInput.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const result = treeInput.map(group => {
|
||||
// Keep active/recent connections and remove the rest
|
||||
if (group.connections) {
|
||||
group.connections = group.connections.filter(con => {
|
||||
if (view === 'active') {
|
||||
return this._connectionManagementService.isConnected(undefined, con);
|
||||
} else if (view === 'recent') {
|
||||
return this._connectionManagementService.isRecent(con);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
group.children = this.filterConnections(group.children, view);
|
||||
// Remove subgroups that are undefined
|
||||
if (group.children) {
|
||||
group.children = group.children.filter(group => {
|
||||
return (group) ? true : false;
|
||||
});
|
||||
}
|
||||
// Return a group only if it has a filtered result or subgroup.
|
||||
if ((group.connections && group.connections.length > 0) || (group.children && group.children.length > 0)) {
|
||||
return group;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set tree elements based on the view (recent/active)
|
||||
*/
|
||||
public showFilteredTree(view: string): void {
|
||||
hide(this.messages);
|
||||
// Clear other action views if user switched between two views
|
||||
this.clearOtherActions(view);
|
||||
const root = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
|
||||
let treeInput: ConnectionProfileGroup = null;
|
||||
if (root) {
|
||||
// Filter results based on view
|
||||
const filteredResults = this.filterConnections([root], view);
|
||||
if (!filteredResults || !filteredResults[0]) {
|
||||
show(this.messages);
|
||||
this.messages.focus();
|
||||
} else {
|
||||
treeInput = filteredResults[0];
|
||||
}
|
||||
this._tree.setInput(treeInput).then(() => {
|
||||
if (isHidden(this.messages)) {
|
||||
this._tree.getFocus();
|
||||
this._tree.expandAll(ConnectionProfileGroup.getSubgroups(treeInput));
|
||||
} else {
|
||||
this._tree.clearFocus();
|
||||
}
|
||||
}, errors.onUnexpectedError);
|
||||
} else {
|
||||
//no op
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches and sets the tree input to the results
|
||||
*/
|
||||
public searchTree(searchString: string): void {
|
||||
if (!searchString) {
|
||||
return;
|
||||
}
|
||||
hide(this.messages);
|
||||
// Clear other actions if user searched during other views
|
||||
this.clearOtherActions();
|
||||
// Filter connections based on search
|
||||
const filteredResults = this.searchConnections(searchString);
|
||||
if (!filteredResults || filteredResults.length === 0) {
|
||||
show(this.messages);
|
||||
this.messages.focus();
|
||||
}
|
||||
// Add all connections to tree root and set tree input
|
||||
const treeInput = new ConnectionProfileGroup('searchroot', undefined, 'searchroot', undefined, undefined);
|
||||
treeInput.addConnections(filteredResults);
|
||||
this._tree.setInput(treeInput).then(() => {
|
||||
if (isHidden(this.messages)) {
|
||||
this._tree.getFocus();
|
||||
this._tree.expandAll(ConnectionProfileGroup.getSubgroups(treeInput));
|
||||
} else {
|
||||
this._tree.clearFocus();
|
||||
}
|
||||
}, errors.onUnexpectedError);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches through all the connections and returns a list of matching connections
|
||||
*/
|
||||
private searchConnections(searchString: string): ConnectionProfile[] {
|
||||
|
||||
const root = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
|
||||
const connections = ConnectionProfileGroup.getConnectionsInGroup(root);
|
||||
const results = connections.filter(con => {
|
||||
if (searchString && (searchString.length > 0)) {
|
||||
return this.isMatch(con, searchString);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the connection matches the search string.
|
||||
* For now, the search criteria is true if the
|
||||
* server name or database name contains the search string (ignores case).
|
||||
*/
|
||||
private isMatch(connection: ConnectionProfile, searchString: string): boolean {
|
||||
searchString = searchString.trim().toLocaleUpperCase();
|
||||
if (this.checkIncludes(searchString, connection.databaseName) || this.checkIncludes(searchString, connection.serverName)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private checkIncludes(searchString: string, candidate: string): boolean {
|
||||
if (candidate && searchString) {
|
||||
return candidate.toLocaleUpperCase().includes(searchString);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the toggle icons for active and recent
|
||||
*/
|
||||
private clearOtherActions(view?: string) {
|
||||
if (!view) {
|
||||
this._activeConnectionsFilterAction.isSet = false;
|
||||
}
|
||||
if (view === 'recent') {
|
||||
this._activeConnectionsFilterAction.isSet = false;
|
||||
}
|
||||
}
|
||||
|
||||
private onSelected(event: any): void {
|
||||
this._treeSelectionHandler.onTreeSelect(event, this._tree, this._connectionManagementService, this._objectExplorerService, () => this._onSelectionOrFocusChange.fire());
|
||||
this._onSelectionOrFocusChange.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of selected nodes in the tree
|
||||
*/
|
||||
public getSelection(): any[] {
|
||||
return this._tree.getSelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the tree view currently has focus
|
||||
*/
|
||||
public isFocused(): boolean {
|
||||
return this._tree.isDOMFocused();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the given element is expanded or collapsed
|
||||
*/
|
||||
public setExpandedState(element: TreeNode | ConnectionProfile, expandedState: TreeItemCollapsibleState): Thenable<void> {
|
||||
if (expandedState === TreeItemCollapsibleState.Collapsed) {
|
||||
return this._tree.collapse(element);
|
||||
} else if (expandedState === TreeItemCollapsibleState.Expanded) {
|
||||
return this._tree.expand(element);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reveal the given element in the tree
|
||||
*/
|
||||
public reveal(element: TreeNode | ConnectionProfile): Thenable<void> {
|
||||
return this._tree.reveal(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the given element in the tree and clear any other selections
|
||||
*/
|
||||
public setSelected(element: TreeNode | ConnectionProfile, selected: boolean, clearOtherSelections: boolean): Thenable<void> {
|
||||
if (clearOtherSelections || (selected && clearOtherSelections !== false)) {
|
||||
this._tree.clearSelection();
|
||||
}
|
||||
if (selected) {
|
||||
this._tree.select(element);
|
||||
return this._tree.reveal(element);
|
||||
} else {
|
||||
this._tree.deselect(element);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given element in the tree is expanded
|
||||
*/
|
||||
public isExpanded(element: TreeNode | ConnectionProfile): boolean {
|
||||
return this._tree.isExpanded(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* dispose the server tree view
|
||||
*/
|
||||
public dispose(): void {
|
||||
this._tree.dispose();
|
||||
this._toDispose = dispose(this._toDispose);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/serverTreeActions';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ServerTreeRenderer } from 'sql/workbench/parts/objectExplorer/browser/serverTreeRenderer';
|
||||
import { ServerTreeDataSource } from 'sql/workbench/parts/objectExplorer/browser/serverTreeDataSource';
|
||||
import { ServerTreeController } from 'sql/workbench/parts/objectExplorer/browser/serverTreeController';
|
||||
import { ServerTreeActionProvider } from 'sql/workbench/parts/objectExplorer/browser/serverTreeActionProvider';
|
||||
import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
import { IController } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ServerTreeDragAndDrop, RecentConnectionsDragAndDrop } from 'sql/workbench/parts/objectExplorer/browser/dragAndDropController';
|
||||
import { RecentConnectionDataSource } from 'sql/workbench/parts/objectExplorer/browser/recentConnectionDataSource';
|
||||
|
||||
export class TreeCreationUtils {
|
||||
/**
|
||||
* Create a Recent Connections tree
|
||||
*/
|
||||
public static createConnectionTree(treeContainer: HTMLElement, instantiationService: IInstantiationService, useController?: IController): Tree {
|
||||
const dataSource = instantiationService.createInstance(RecentConnectionDataSource);
|
||||
const renderer = instantiationService.createInstance(ServerTreeRenderer, true);
|
||||
const controller = useController ? useController : new DefaultController();
|
||||
const dnd = instantiationService.createInstance(RecentConnectionsDragAndDrop);
|
||||
const filter = new DefaultFilter();
|
||||
const sorter = undefined;
|
||||
const accessibilityProvider = new DefaultAccessibilityProvider();
|
||||
|
||||
return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
|
||||
{
|
||||
indentPixels: 0,
|
||||
twistiePixels: 0,
|
||||
ariaLabel: nls.localize('treeAriaLabel', "Recent Connections")
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Servers viewlet tree
|
||||
*/
|
||||
public static createRegisteredServersTree(treeContainer: HTMLElement, instantiationService: IInstantiationService): Tree {
|
||||
|
||||
const dataSource = instantiationService.createInstance(ServerTreeDataSource);
|
||||
const actionProvider = instantiationService.createInstance(ServerTreeActionProvider);
|
||||
const renderer = instantiationService.createInstance(ServerTreeRenderer, false);
|
||||
const controller = instantiationService.createInstance(ServerTreeController, actionProvider);
|
||||
const dnd = instantiationService.createInstance(ServerTreeDragAndDrop);
|
||||
const filter = new DefaultFilter();
|
||||
const sorter = undefined;
|
||||
const accessibilityProvider = new DefaultAccessibilityProvider();
|
||||
|
||||
return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
|
||||
{
|
||||
indentPixels: 10,
|
||||
twistiePixels: 20,
|
||||
ariaLabel: nls.localize('treeCreation.regTreeAriaLabel', "Servers")
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConnectionManagementService, IConnectionCompletionOptions } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
|
||||
import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import { TreeUpdateUtils } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
|
||||
export class TreeSelectionHandler {
|
||||
progressRunner: IProgressRunner;
|
||||
|
||||
private _clicks: number = 0;
|
||||
private _doubleClickTimeoutTimer: NodeJS.Timer = undefined;
|
||||
|
||||
constructor(@IProgressService private _progressService: IProgressService) {
|
||||
|
||||
}
|
||||
|
||||
public onTreeActionStateChange(started: boolean): void {
|
||||
if (this.progressRunner) {
|
||||
this.progressRunner.done();
|
||||
}
|
||||
|
||||
if (started) {
|
||||
this.progressRunner = this._progressService.show(true);
|
||||
} else {
|
||||
this.progressRunner = null;
|
||||
}
|
||||
}
|
||||
|
||||
private isMouseEvent(event: any): boolean {
|
||||
return event && event.payload && event.payload.origin === 'mouse';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle selection of tree element
|
||||
*/
|
||||
public onTreeSelect(event: any, tree: ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, connectionCompleteCallback: () => void) {
|
||||
if (this.isMouseEvent(event)) {
|
||||
this._clicks++;
|
||||
}
|
||||
|
||||
// clear pending click timeouts to avoid sending multiple events on double-click
|
||||
if (this._doubleClickTimeoutTimer) {
|
||||
clearTimeout(this._doubleClickTimeoutTimer);
|
||||
}
|
||||
|
||||
let isKeyboard = event && event.payload && event.payload.origin === 'keyboard';
|
||||
|
||||
// grab the current selection for use later
|
||||
let selection = tree.getSelection();
|
||||
|
||||
this._doubleClickTimeoutTimer = setTimeout(() => {
|
||||
// don't send tree update events while dragging
|
||||
if (!TreeUpdateUtils.isInDragAndDrop) {
|
||||
let isDoubleClick = this._clicks > 1;
|
||||
this.handleTreeItemSelected(connectionManagementService, objectExplorerService, isDoubleClick, isKeyboard, selection, tree, connectionCompleteCallback);
|
||||
}
|
||||
this._clicks = 0;
|
||||
this._doubleClickTimeoutTimer = undefined;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param connectionManagementService
|
||||
* @param objectExplorerService
|
||||
* @param isDoubleClick
|
||||
* @param isKeyboard
|
||||
* @param selection
|
||||
* @param tree
|
||||
* @param connectionCompleteCallback A function that gets called after a connection is established due to the selection, if needed
|
||||
*/
|
||||
private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: ITree, connectionCompleteCallback: () => void): void {
|
||||
let connectionProfile: ConnectionProfile = undefined;
|
||||
let options: IConnectionCompletionOptions = {
|
||||
params: undefined,
|
||||
saveTheConnection: true,
|
||||
showConnectionDialogOnError: true,
|
||||
showFirewallRuleOnError: true,
|
||||
showDashboard: isDoubleClick // only show the dashboard if the action is double click
|
||||
};
|
||||
if (selection && selection.length > 0 && (selection[0] instanceof ConnectionProfile)) {
|
||||
connectionProfile = <ConnectionProfile>selection[0];
|
||||
|
||||
if (connectionProfile) {
|
||||
this.onTreeActionStateChange(true);
|
||||
|
||||
TreeUpdateUtils.connectAndCreateOeSession(connectionProfile, options, connectionManagementService, objectExplorerService, tree).then(sessionCreated => {
|
||||
if (!sessionCreated) {
|
||||
this.onTreeActionStateChange(false);
|
||||
}
|
||||
if (connectionCompleteCallback) {
|
||||
connectionCompleteCallback();
|
||||
}
|
||||
}, error => {
|
||||
this.onTreeActionStateChange(false);
|
||||
});
|
||||
}
|
||||
} else if (isDoubleClick && selection && selection.length > 0 && (selection[0] instanceof TreeNode)) {
|
||||
let treeNode = selection[0];
|
||||
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
|
||||
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
|
||||
if (connectionProfile) {
|
||||
connectionManagementService.showDashboard(connectionProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isKeyboard) {
|
||||
tree.toggleExpansion(selection[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { IConnectionManagementService, IConnectionCompletionOptions, IConnectionCallbacks } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import { NodeType } from 'sql/workbench/parts/objectExplorer/common/nodeType';
|
||||
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
|
||||
export interface IExpandableTree extends ITree {
|
||||
// {{SQL CARBON EDIT }} - add back deleted VS Code tree methods
|
||||
/**
|
||||
* Returns a list of the currently expanded elements.
|
||||
*/
|
||||
getExpandedElements(): any[];
|
||||
|
||||
/**
|
||||
* Returns a number between 0 and 1 representing how much the tree is scroll down. 0 means all the way
|
||||
* to the top; 1 means all the way down.
|
||||
*/
|
||||
getScrollPosition(): number;
|
||||
|
||||
/**
|
||||
* Sets the scroll position with a number between 0 and 1 representing how much the tree is scroll down. 0 means all the way
|
||||
* to the top; 1 means all the way down.
|
||||
*/
|
||||
setScrollPosition(pos: number): void;
|
||||
|
||||
/**
|
||||
* Returns the total height of the tree's content.
|
||||
*/
|
||||
getContentHeight(): number;
|
||||
// {{SQL CARBON EDIT }} - end block
|
||||
}
|
||||
|
||||
|
||||
export class TreeUpdateUtils {
|
||||
|
||||
public static isInDragAndDrop: boolean = false;
|
||||
|
||||
/**
|
||||
* Set input for the tree.
|
||||
*/
|
||||
public static structuralTreeUpdate(tree: ITree, viewKey: string, connectionManagementService: IConnectionManagementService, providers?: string[]): Promise<void> {
|
||||
// convert to old VS Code tree interface with expandable methods
|
||||
let expandableTree: IExpandableTree = <IExpandableTree>tree;
|
||||
|
||||
let selectedElement: any;
|
||||
let targetsToExpand: any[];
|
||||
if (tree) {
|
||||
let selection = tree.getSelection();
|
||||
if (selection && selection.length === 1) {
|
||||
selectedElement = <any>selection[0];
|
||||
}
|
||||
targetsToExpand = expandableTree.getExpandedElements();
|
||||
}
|
||||
let groups;
|
||||
let treeInput = new ConnectionProfileGroup('root', null, undefined, undefined, undefined);
|
||||
if (viewKey === 'recent') {
|
||||
groups = connectionManagementService.getRecentConnections(providers);
|
||||
treeInput.addConnections(groups);
|
||||
} else if (viewKey === 'active') {
|
||||
groups = connectionManagementService.getActiveConnections(providers);
|
||||
treeInput.addConnections(groups);
|
||||
} else if (viewKey === 'saved') {
|
||||
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService, providers);
|
||||
}
|
||||
|
||||
return tree.setInput(treeInput).then(() => {
|
||||
// Make sure to expand all folders that where expanded in the previous session
|
||||
if (targetsToExpand) {
|
||||
tree.expandAll(targetsToExpand);
|
||||
}
|
||||
if (selectedElement) {
|
||||
tree.select(selectedElement);
|
||||
}
|
||||
tree.getFocus();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set input for the registered servers tree.
|
||||
*/
|
||||
public static registeredServerUpdate(tree: ITree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): Promise<void> {
|
||||
// convert to old VS Code tree interface with expandable methods
|
||||
let expandableTree: IExpandableTree = <IExpandableTree>tree;
|
||||
|
||||
let selectedElement: any = elementToSelect;
|
||||
let targetsToExpand: any[];
|
||||
|
||||
// Focus
|
||||
tree.domFocus();
|
||||
|
||||
if (tree) {
|
||||
let selection = tree.getSelection();
|
||||
if (!selectedElement) {
|
||||
if (selection && selection.length === 1) {
|
||||
selectedElement = <any>selection[0];
|
||||
}
|
||||
}
|
||||
targetsToExpand = expandableTree.getExpandedElements();
|
||||
if (selectedElement && targetsToExpand.indexOf(selectedElement) === -1) {
|
||||
targetsToExpand.push(selectedElement);
|
||||
}
|
||||
}
|
||||
|
||||
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||
if (treeInput) {
|
||||
if (treeInput !== tree.getInput()) {
|
||||
return tree.setInput(treeInput).then(() => {
|
||||
// Make sure to expand all folders that where expanded in the previous session
|
||||
if (targetsToExpand) {
|
||||
tree.expandAll(targetsToExpand);
|
||||
}
|
||||
if (selectedElement) {
|
||||
tree.select(selectedElement);
|
||||
}
|
||||
tree.getFocus();
|
||||
}, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public static getTreeInput(connectionManagementService: IConnectionManagementService, providers?: string[]): ConnectionProfileGroup {
|
||||
|
||||
let groups = connectionManagementService.getConnectionGroups(providers);
|
||||
if (groups && groups.length > 0) {
|
||||
let treeInput = groups[0];
|
||||
treeInput.name = 'root';
|
||||
return treeInput;
|
||||
}
|
||||
// Should never get to this case.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public static hasObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService): boolean {
|
||||
let isConnected = connectionManagementService.isConnected(undefined, connection);
|
||||
return isConnected;
|
||||
}
|
||||
|
||||
public static connectIfNotConnected(
|
||||
connection: IConnectionProfile,
|
||||
options: IConnectionCompletionOptions,
|
||||
connectionManagementService: IConnectionManagementService,
|
||||
tree: ITree): Promise<ConnectionProfile> {
|
||||
return new Promise<ConnectionProfile>((resolve, reject) => {
|
||||
if (!connectionManagementService.isProfileConnected(connection)) {
|
||||
// don't try to reconnect if currently connecting
|
||||
if (connectionManagementService.isProfileConnecting(connection)) {
|
||||
resolve(undefined);
|
||||
|
||||
// else if we aren't connected or connecting then try to connect
|
||||
} else {
|
||||
let callbacks: IConnectionCallbacks = undefined;
|
||||
if (tree) {
|
||||
// Show the spinner in OE by adding the 'loading' trait to the connection, and set up callbacks to hide the spinner
|
||||
tree.addTraits('loading', [connection]);
|
||||
let rejectOrCancelCallback = () => {
|
||||
tree.collapse(connection);
|
||||
tree.removeTraits('loading', [connection]);
|
||||
};
|
||||
callbacks = {
|
||||
onConnectStart: undefined,
|
||||
onConnectReject: rejectOrCancelCallback,
|
||||
onConnectSuccess: () => tree.removeTraits('loading', [connection]),
|
||||
onDisconnect: undefined,
|
||||
onConnectCanceled: rejectOrCancelCallback,
|
||||
};
|
||||
}
|
||||
connectionManagementService.connect(connection, undefined, options, callbacks).then(result => {
|
||||
if (result.connected) {
|
||||
let existingConnection = connectionManagementService.findExistingConnection(connection);
|
||||
resolve(existingConnection);
|
||||
} else {
|
||||
reject('connection failed');
|
||||
}
|
||||
}, connectionError => {
|
||||
reject(connectionError);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let existingConnection = connectionManagementService.findExistingConnection(connection);
|
||||
if (options && options.showDashboard) {
|
||||
connectionManagementService.showDashboard(connection).then((value) => {
|
||||
resolve(existingConnection);
|
||||
});
|
||||
} else {
|
||||
resolve(existingConnection);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a connection if the not already connected and try to create new object explorer session
|
||||
* I the profile is already connected, tries to do the action requested in the options (e.g. open dashboard)
|
||||
* Returns true if new object explorer session created for the connection, otherwise returns false
|
||||
* @param connection Connection Profile
|
||||
* @param options Includes the actions to happened after connection is made
|
||||
* @param connectionManagementService Connection management service instance
|
||||
* @param objectExplorerService Object explorer service instance
|
||||
*/
|
||||
public static connectAndCreateOeSession(connection: IConnectionProfile, options: IConnectionCompletionOptions,
|
||||
connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: ITree): Promise<boolean> {
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
TreeUpdateUtils.connectIfNotConnected(connection, options, connectionManagementService, tree).then(connectedConnection => {
|
||||
if (connectedConnection) {
|
||||
// append group ID and original display name to build unique OE session ID
|
||||
connectedConnection.options['groupId'] = connection.groupId;
|
||||
connectedConnection.options['databaseDisplayName'] = connection.databaseName;
|
||||
|
||||
let rootNode: TreeNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
|
||||
if (!rootNode) {
|
||||
objectExplorerService.updateObjectExplorerNodes(connectedConnection).then(() => {
|
||||
rootNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
|
||||
resolve(true);
|
||||
// The oe request is sent. an event will be raised when the session is created
|
||||
}, error => {
|
||||
reject('session failed');
|
||||
});
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
}, connectionError => {
|
||||
reject(connectionError);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static getObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService): Promise<TreeNode[]> {
|
||||
return new Promise<TreeNode[]>((resolve, reject) => {
|
||||
if (connection.isDisconnecting) {
|
||||
resolve([]);
|
||||
} else {
|
||||
let rootNode = objectExplorerService.getObjectExplorerNode(connection);
|
||||
if (rootNode) {
|
||||
objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode).then(() => {
|
||||
resolve(rootNode.children);
|
||||
}, expandError => {
|
||||
resolve([]);
|
||||
});
|
||||
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getObjectExplorerParent(objectExplorerNode: TreeNode, connectionManagementService: IConnectionManagementService): any {
|
||||
if (objectExplorerNode && objectExplorerNode.parent) {
|
||||
// if object explorer node's parent is root, return connection profile
|
||||
if (!objectExplorerNode.parent.parent) {
|
||||
let connectionId = objectExplorerNode.getConnectionProfile().id;
|
||||
|
||||
// get connection profile from connection profile groups
|
||||
let root = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||
let connections = ConnectionProfileGroup.getConnectionsInGroup(root);
|
||||
let results = connections.filter(con => {
|
||||
if (connectionId === con.id) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (results && results.length > 0) {
|
||||
return results[0];
|
||||
}
|
||||
} else {
|
||||
return objectExplorerNode.parent;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param treeNode Returns true if the tree node is a database node
|
||||
*/
|
||||
public static isDatabaseNode(treeNode: TreeNode): boolean {
|
||||
return treeNode && treeNode.nodeTypeId === NodeType.Database;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param treeNode Returns true if the tree node is an available database node
|
||||
*/
|
||||
public static isAvailableDatabaseNode(treeNode: TreeNode): boolean {
|
||||
return treeNode && treeNode.nodeTypeId === NodeType.Database && treeNode.nodeStatus !== 'Unavailable';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection profile with the current database
|
||||
*/
|
||||
public static getConnectionProfile(treeNode: TreeNode): ConnectionProfile {
|
||||
let connectionProfile = treeNode.getConnectionProfile();
|
||||
let databaseName = treeNode.getDatabaseName();
|
||||
if (databaseName !== undefined && connectionProfile.databaseName !== databaseName) {
|
||||
connectionProfile = connectionProfile.cloneWithDatabase(databaseName);
|
||||
}
|
||||
return connectionProfile;
|
||||
}
|
||||
}
|
||||
100
src/sql/workbench/parts/objectExplorer/common/nodeType.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export class NodeType {
|
||||
public static Folder = 'Folder';
|
||||
public static Root = 'root';
|
||||
public static Database = 'Database';
|
||||
public static Server = 'Server';
|
||||
public static ScalarValuedFunction = 'ScalarValuedFunction';
|
||||
public static TableValuedFunction = 'TableValuedFunction';
|
||||
public static AggregateFunction = 'AggregateFunction';
|
||||
public static FileGroup = 'FileGroup';
|
||||
public static StoredProcedure = 'StoredProcedure';
|
||||
public static UserDefinedTableType = 'UserDefinedTableType';
|
||||
public static View = 'View';
|
||||
public static Table = 'Table';
|
||||
public static HistoryTable = 'HistoryTable';
|
||||
public static ServerLevelLinkedServerLogin = 'ServerLevelLinkedServerLogin';
|
||||
public static ServerLevelServerAudit = 'ServerLevelServerAudit';
|
||||
public static ServerLevelCryptographicProvider = 'ServerLevelCryptographicProvider';
|
||||
public static ServerLevelCredential = 'ServerLevelCredential';
|
||||
public static ServerLevelServerRole = 'ServerLevelServerRole';
|
||||
public static ServerLevelLogin = 'ServerLevelLogin';
|
||||
public static ServerLevelServerAuditSpecification = 'ServerLevelServerAuditSpecification';
|
||||
public static ServerLevelServerTrigger = 'ServerLevelServerTrigger';
|
||||
public static ServerLevelLinkedServer = 'ServerLevelLinkedServer';
|
||||
public static ServerLevelEndpoint = 'ServerLevelEndpoint';
|
||||
public static Synonym = 'Synonym';
|
||||
public static DatabaseTrigger = 'DatabaseTrigger';
|
||||
public static Assembly = 'Assembly';
|
||||
public static MessageType = 'MessageType';
|
||||
public static Contract = 'Contract';
|
||||
public static Queue = 'Queue';
|
||||
public static Service = 'Service';
|
||||
public static Route = 'Route';
|
||||
public static DatabaseAndQueueEventNotification = 'DatabaseAndQueueEventNotification';
|
||||
public static RemoteServiceBinding = 'RemoteServiceBinding';
|
||||
public static BrokerPriority = 'BrokerPriority';
|
||||
public static FullTextCatalog = 'FullTextCatalog';
|
||||
public static FullTextStopList = 'FullTextStopList';
|
||||
public static SqlLogFile = 'SqlLogFile';
|
||||
public static PartitionFunction = 'PartitionFunction';
|
||||
public static PartitionScheme = 'PartitionScheme';
|
||||
public static SearchPropertyList = 'SearchPropertyList';
|
||||
public static User = 'User';
|
||||
public static Schema = 'Schema';
|
||||
public static AsymmetricKey = 'AsymmetricKey';
|
||||
public static Certificate = 'Certificate';
|
||||
public static SymmetricKey = 'SymmetricKey';
|
||||
public static DatabaseEncryptionKey = 'DatabaseEncryptionKey';
|
||||
public static MasterKey = 'MasterKey';
|
||||
public static DatabaseAuditSpecification = 'DatabaseAuditSpecification';
|
||||
public static Column = 'Column';
|
||||
public static Key = 'Key';
|
||||
public static Constraint = 'Constraint';
|
||||
public static Trigger = 'Trigger';
|
||||
public static Index = 'Index';
|
||||
public static Statistic = 'Statistic';
|
||||
public static UserDefinedDataType = 'UserDefinedDataType';
|
||||
public static UserDefinedType = 'UserDefinedType';
|
||||
public static XmlSchemaCollection = 'XmlSchemaCollection';
|
||||
public static SystemExactNumeric = 'SystemExactNumeric';
|
||||
public static SystemApproximateNumeric = 'SystemApproximateNumeric';
|
||||
public static SystemDateAndTime = 'SystemDateAndTime';
|
||||
public static SystemCharacterString = 'SystemCharacterString';
|
||||
public static SystemUnicodeCharacterString = 'SystemUnicodeCharacterString';
|
||||
public static SystemBinaryString = 'SystemBinaryString';
|
||||
public static SystemOtherDataType = 'SystemOtherDataType';
|
||||
public static SystemClrDataType = 'SystemClrDataType';
|
||||
public static SystemSpatialDataType = 'SystemSpatialDataType';
|
||||
public static UserDefinedTableTypeColumn = 'UserDefinedTableTypeColumn';
|
||||
public static UserDefinedTableTypeKey = 'UserDefinedTableTypeKey';
|
||||
public static UserDefinedTableTypeConstraint = 'UserDefinedTableTypeConstraint';
|
||||
public static StoredProcedureParameter = 'StoredProcedureParameter';
|
||||
public static TableValuedFunctionParameter = 'TableValuedFunctionParameter';
|
||||
public static ScalarValuedFunctionParameter = 'ScalarValuedFunctionParameter';
|
||||
public static AggregateFunctionParameter = 'AggregateFunctionParameter';
|
||||
public static DatabaseRole = 'DatabaseRole';
|
||||
public static ApplicationRole = 'ApplicationRole';
|
||||
public static FileGroupFile = 'FileGroupFile';
|
||||
public static SystemMessageType = 'SystemMessageType';
|
||||
public static SystemContract = 'SystemContract';
|
||||
public static SystemService = 'SystemService';
|
||||
public static SystemQueue = 'SystemQueue';
|
||||
public static Sequence = 'Sequence';
|
||||
public static SecurityPolicy = 'SecurityPolicy';
|
||||
public static DatabaseScopedCredential = 'DatabaseScopedCredential';
|
||||
public static ExternalResource = 'ExternalResource';
|
||||
public static ExternalDataSource = 'ExternalDataSource';
|
||||
public static ExternalFileFormat = 'ExternalFileFormat';
|
||||
public static ExternalTable = 'ExternalTable';
|
||||
public static ColumnMasterKey = 'ColumnMasterKey';
|
||||
public static ColumnEncryptionKey = 'ColumnEncryptionKey';
|
||||
}
|
||||
|
||||
export interface SqlThemeIcon {
|
||||
readonly id: string;
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { ConnectionType, IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { ITreeItem } from 'sql/workbench/common/views';
|
||||
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
|
||||
import { hash } from 'vs/base/common/hash';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TreeItemCollapsibleState } from 'vs/workbench/common/views';
|
||||
|
||||
export const SERVICE_ID = 'oeShimService';
|
||||
export const IOEShimService = createDecorator<IOEShimService>(SERVICE_ID);
|
||||
|
||||
export interface IOEShimService {
|
||||
_serviceBrand: any;
|
||||
getChildren(node: ITreeItem, viewId: string): Promise<ITreeItem[]>;
|
||||
disconnectNode(viewId: string, node: ITreeItem): Promise<boolean>;
|
||||
providerExists(providerId: string): boolean;
|
||||
isNodeConnected(viewId: string, node: ITreeItem): boolean;
|
||||
}
|
||||
|
||||
export class OEShimService extends Disposable implements IOEShimService {
|
||||
_serviceBrand: any;
|
||||
|
||||
private sessionMap = new Map<number, string>();
|
||||
private nodeHandleMap = new Map<number, string>();
|
||||
|
||||
constructor(
|
||||
@IObjectExplorerService private oe: IObjectExplorerService,
|
||||
@IConnectionManagementService private cm: IConnectionManagementService,
|
||||
@IConnectionDialogService private cd: IConnectionDialogService,
|
||||
@ICapabilitiesService private capabilities: ICapabilitiesService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
private async createSession(viewId: string, providerId: string, node: ITreeItem): Promise<string> {
|
||||
let deferred = new Deferred<string>();
|
||||
let connProfile = new ConnectionProfile(this.capabilities, node.payload);
|
||||
connProfile.saveProfile = false;
|
||||
if (this.cm.providerRegistered(providerId)) {
|
||||
let userProfile = await this.cd.openDialogAndWait(this.cm, { connectionType: ConnectionType.default, showDashboard: false }, connProfile, undefined, false);
|
||||
if (userProfile) {
|
||||
connProfile = new ConnectionProfile(this.capabilities, userProfile);
|
||||
} else {
|
||||
return Promise.reject('User canceled');
|
||||
}
|
||||
}
|
||||
let sessionResp = await this.oe.createNewSession(providerId, connProfile);
|
||||
let disp = this.oe.onUpdateObjectExplorerNodes(e => {
|
||||
if (e.connection.id === connProfile.id) {
|
||||
if (e.errorMessage) {
|
||||
deferred.reject();
|
||||
return;
|
||||
}
|
||||
let rootNode = this.oe.getSession(sessionResp.sessionId).rootNode;
|
||||
// this is how we know it was shimmed
|
||||
if (rootNode.nodePath) {
|
||||
this.nodeHandleMap.set(generateNodeMapKey(viewId, node), rootNode.nodePath);
|
||||
}
|
||||
}
|
||||
disp.dispose();
|
||||
deferred.resolve(sessionResp.sessionId);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
public async disconnectNode(viewId: string, node: ITreeItem): Promise<boolean> {
|
||||
// we assume only nodes with payloads can be connected
|
||||
// check to make sure we have an existing connection
|
||||
let key = generateSessionMapKey(viewId, node);
|
||||
let session = this.sessionMap.get(key);
|
||||
if (session) {
|
||||
let closed = (await this.oe.closeSession(node.childProvider, this.oe.getSession(session))).success;
|
||||
if (closed) {
|
||||
this.sessionMap.delete(key);
|
||||
}
|
||||
return closed;
|
||||
}
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
private async getOrCreateSession(viewId: string, node: ITreeItem): Promise<string> {
|
||||
// verify the map is correct
|
||||
let key = generateSessionMapKey(viewId, node);
|
||||
if (!this.sessionMap.has(key)) {
|
||||
this.sessionMap.set(key, await this.createSession(viewId, node.childProvider, node));
|
||||
}
|
||||
return this.sessionMap.get(key);
|
||||
}
|
||||
|
||||
public async getChildren(node: ITreeItem, viewId: string): Promise<ITreeItem[]> {
|
||||
if (node.payload) {
|
||||
let sessionId = await this.getOrCreateSession(viewId, node);
|
||||
let requestHandle = this.nodeHandleMap.get(generateNodeMapKey(viewId, node)) || node.handle;
|
||||
let treeNode = new TreeNode(undefined, undefined, undefined, requestHandle, undefined, undefined, undefined, undefined, undefined, undefined);
|
||||
treeNode.connection = new ConnectionProfile(this.capabilities, node.payload);
|
||||
return this.oe.resolveTreeNodeChildren({
|
||||
success: undefined,
|
||||
sessionId,
|
||||
rootNode: undefined,
|
||||
errorMessage: undefined
|
||||
}, treeNode).then(e => e.map(n => this.treeNodeToITreeItem(viewId, n, node)));
|
||||
} else {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
}
|
||||
|
||||
private treeNodeToITreeItem(viewId: string, node: TreeNode, parentNode: ITreeItem): ITreeItem {
|
||||
let handle = generateUuid();
|
||||
let nodePath = node.nodePath;
|
||||
let icon: string = '';
|
||||
if (node.iconType) {
|
||||
icon = (typeof node.iconType === 'string') ? node.iconType : node.iconType.id;
|
||||
} else {
|
||||
icon = node.nodeTypeId;
|
||||
if (node.nodeStatus) {
|
||||
icon = node.nodeTypeId + '_' + node.nodeStatus;
|
||||
}
|
||||
if (node.nodeSubType) {
|
||||
icon = node.nodeTypeId + '_' + node.nodeSubType;
|
||||
}
|
||||
}
|
||||
icon = icon.toLowerCase();
|
||||
let newTreeItem: ITreeItem = {
|
||||
parentHandle: node.parent.id,
|
||||
handle,
|
||||
collapsibleState: node.isAlwaysLeaf ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
|
||||
label: {
|
||||
label: node.label
|
||||
},
|
||||
childProvider: node.childProvider || parentNode.childProvider,
|
||||
providerHandle: parentNode.childProvider,
|
||||
payload: node.payload || parentNode.payload,
|
||||
contextValue: node.nodeTypeId,
|
||||
sqlIcon: icon
|
||||
};
|
||||
this.nodeHandleMap.set(generateNodeMapKey(viewId, newTreeItem), nodePath);
|
||||
return newTreeItem;
|
||||
}
|
||||
|
||||
public providerExists(providerId: string): boolean {
|
||||
return this.oe.providerRegistered(providerId);
|
||||
}
|
||||
|
||||
public isNodeConnected(viewId: string, node: ITreeItem): boolean {
|
||||
return this.sessionMap.has(generateSessionMapKey(viewId, node));
|
||||
}
|
||||
}
|
||||
|
||||
function generateSessionMapKey(viewId: string, node: ITreeItem): number {
|
||||
return hash([viewId, node.childProvider, node.payload]);
|
||||
}
|
||||
|
||||
function generateNodeMapKey(viewId: string, node: ITreeItem): number {
|
||||
return hash([viewId, node.handle]);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConfigurationRegistry, Extensions, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
|
||||
export const SERVER_GROUP_CONFIG = 'serverGroup';
|
||||
export const SERVER_GROUP_COLORS_CONFIG = 'colors';
|
||||
export const SERVER_GROUP_AUTOEXPAND_CONFIG = 'autoExpand';
|
||||
|
||||
const serverGroupConfig: IConfigurationNode = {
|
||||
id: 'Server Groups',
|
||||
type: 'object',
|
||||
properties: {
|
||||
[SERVER_GROUP_CONFIG + '.' + SERVER_GROUP_COLORS_CONFIG]: <IJSONSchema>{
|
||||
type: 'array',
|
||||
items: 'string',
|
||||
'description': localize('serverGroup.colors', 'Server Group color palette used in the Object Explorer viewlet.'),
|
||||
default: [
|
||||
'#A1634D',
|
||||
'#7F0000',
|
||||
'#914576',
|
||||
'#85AE72',
|
||||
'#98AFC7',
|
||||
'#4452A6',
|
||||
'#6A6599',
|
||||
'#515151'
|
||||
]
|
||||
},
|
||||
[SERVER_GROUP_CONFIG + '.' + SERVER_GROUP_AUTOEXPAND_CONFIG]: {
|
||||
'type': 'boolean',
|
||||
'description': localize('serverGroup.autoExpand', 'Auto-expand Server Groups in the Object Explorer viewlet.'),
|
||||
'default': 'true'
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
configurationRegistry.registerConfiguration(serverGroupConfig);
|
||||
@@ -0,0 +1,70 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
|
||||
import * as TypeChecker from 'vs/base/common/types';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
export class ServerGroupViewModel {
|
||||
public groupName: string;
|
||||
public groupDescription: string;
|
||||
public groupColor: string;
|
||||
public colors: string[] = ['#515151', '#004760', '#771b00', '#700060', '#a17d01', '#006749', '#654502', '#3A0293'];
|
||||
|
||||
private _domainModel: IConnectionProfileGroup;
|
||||
private _editMode: boolean;
|
||||
private readonly _addServerGroupTitle: string = localize('serverGroup.addServerGroup', 'Add server group');
|
||||
private readonly _editServerGroupTitle: string = localize('serverGroup.editServerGroup', 'Edit server group');
|
||||
private readonly _defaultColor: string = '#515151';
|
||||
|
||||
constructor(domainModel?: IConnectionProfileGroup, colors?: string[]) {
|
||||
// keep reference to domain model to be able to see if there are pending changes
|
||||
if (domainModel) {
|
||||
this._domainModel = domainModel;
|
||||
|
||||
// initialize the view model properties
|
||||
this.groupName = domainModel.name;
|
||||
this.groupColor = domainModel.color;
|
||||
this.groupDescription = domainModel.description;
|
||||
|
||||
this._editMode = true;
|
||||
}
|
||||
else {
|
||||
// initialize defaults for a new group
|
||||
this.groupName = '';
|
||||
this.groupDescription = '';
|
||||
this.groupColor = this._defaultColor;
|
||||
|
||||
this._editMode = false;
|
||||
}
|
||||
|
||||
if (colors) {
|
||||
this.colors = colors;
|
||||
}
|
||||
}
|
||||
|
||||
// check to see if the current state of the view model is different than the data in the domain model
|
||||
public hasPendingChanges(): boolean {
|
||||
if (!TypeChecker.isUndefinedOrNull(this._domainModel)) {
|
||||
return ((strings.isFalsyOrWhitespace(this.groupName) === false) &&
|
||||
((this.groupName !== this._domainModel.name) ||
|
||||
(this.groupDescription !== this._domainModel.description) ||
|
||||
(this.groupColor !== this._domainModel.color)));
|
||||
}
|
||||
else {
|
||||
return (strings.isFalsyOrWhitespace(this.groupName) === false);
|
||||
}
|
||||
}
|
||||
|
||||
public getDialogTitle(): string {
|
||||
if (this._editMode === true) {
|
||||
return this._editServerGroupTitle;
|
||||
} else {
|
||||
return this._addServerGroupTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
175
src/sql/workbench/parts/objectExplorer/common/treeNode.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { NodeType, SqlThemeIcon } from 'sql/workbench/parts/objectExplorer/common/nodeType';
|
||||
import * as azdata from 'sqlops';
|
||||
|
||||
import * as UUID from 'vs/base/common/uuid';
|
||||
|
||||
export enum TreeItemCollapsibleState {
|
||||
None = 0,
|
||||
Collapsed = 1,
|
||||
Expanded = 2
|
||||
}
|
||||
|
||||
export interface ObjectExplorerCallbacks {
|
||||
getChildren(treeNode: TreeNode): Thenable<TreeNode[]>;
|
||||
isExpanded(treeNode: TreeNode): Thenable<boolean>;
|
||||
setNodeExpandedState(TreeNode: TreeNode, expandedState: TreeItemCollapsibleState): Thenable<void>;
|
||||
setNodeSelected(TreeNode: TreeNode, selected: boolean, clearOtherSelections?: boolean): Thenable<void>;
|
||||
}
|
||||
|
||||
export class TreeNode {
|
||||
/**
|
||||
* Informs who provides the children to a node, used by data explorer tree view api
|
||||
*/
|
||||
public childProvider: string;
|
||||
/**
|
||||
* Holds the connection profile for nodes, used by data explorer tree view api
|
||||
*/
|
||||
public payload: any;
|
||||
/**
|
||||
* id for TreeNode
|
||||
*/
|
||||
public id: string;
|
||||
|
||||
/**
|
||||
* string defining the type of the node - for example Server, Database, Folder, Table
|
||||
*/
|
||||
public nodeTypeId: string;
|
||||
|
||||
/**
|
||||
* Label to display to the user, describing this node
|
||||
*/
|
||||
public label: string;
|
||||
|
||||
/**
|
||||
* Is this a leaf node (in which case no children can be generated) or is it expandable?
|
||||
*/
|
||||
public isAlwaysLeaf: boolean;
|
||||
|
||||
/**
|
||||
* Message to show if this Node is in an error state. This indicates
|
||||
* that children could be retrieved
|
||||
*/
|
||||
public errorStateMessage: string;
|
||||
|
||||
/**
|
||||
* Parent of this node
|
||||
*/
|
||||
public parent: TreeNode;
|
||||
|
||||
/**
|
||||
* Path identifying this node
|
||||
*/
|
||||
public nodePath: string;
|
||||
|
||||
/**
|
||||
* Node sub type
|
||||
*/
|
||||
public nodeSubType: string;
|
||||
|
||||
/**
|
||||
* Node Status
|
||||
*/
|
||||
public nodeStatus: string;
|
||||
|
||||
/**
|
||||
* Children of this node
|
||||
*/
|
||||
public children: TreeNode[];
|
||||
|
||||
|
||||
public connection: ConnectionProfile;
|
||||
|
||||
public session: azdata.ObjectExplorerSession;
|
||||
|
||||
public metadata: azdata.ObjectMetadata;
|
||||
|
||||
public iconType: string | SqlThemeIcon;
|
||||
|
||||
constructor(nodeTypeId: string, label: string, isAlwaysLeaf: boolean, nodePath: string,
|
||||
nodeSubType: string, nodeStatus: string, parent: TreeNode, metadata: azdata.ObjectMetadata,
|
||||
iconType: string | SqlThemeIcon,
|
||||
private _objectExplorerCallbacks: ObjectExplorerCallbacks) {
|
||||
this.nodeTypeId = nodeTypeId;
|
||||
this.label = label;
|
||||
this.isAlwaysLeaf = isAlwaysLeaf;
|
||||
this.nodePath = nodePath;
|
||||
this.parent = parent;
|
||||
this.metadata = metadata;
|
||||
this.iconType = iconType;
|
||||
this.id = UUID.generateUuid();
|
||||
this.nodeSubType = nodeSubType;
|
||||
this.nodeStatus = nodeStatus;
|
||||
}
|
||||
public getConnectionProfile(): ConnectionProfile {
|
||||
var currentNode: TreeNode = this;
|
||||
while (!currentNode.connection && currentNode.parent) {
|
||||
currentNode = currentNode.parent;
|
||||
}
|
||||
return currentNode.connection;
|
||||
}
|
||||
|
||||
public getDatabaseName(): string {
|
||||
if (this.connection) {
|
||||
return undefined;
|
||||
}
|
||||
var currentNode: TreeNode = this;
|
||||
while (currentNode.nodeTypeId !== NodeType.Database && currentNode.nodeTypeId !== NodeType.Server && currentNode.parent) {
|
||||
currentNode = currentNode.parent;
|
||||
}
|
||||
|
||||
if (currentNode && currentNode.nodeTypeId === NodeType.Database) {
|
||||
return currentNode.metadata ? currentNode.metadata.name : null;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public getSession(): azdata.ObjectExplorerSession {
|
||||
var currentNode: TreeNode = this;
|
||||
while (!currentNode.session && currentNode.parent) {
|
||||
currentNode = currentNode.parent;
|
||||
}
|
||||
return currentNode.session;
|
||||
}
|
||||
|
||||
public isTopLevel(): boolean {
|
||||
if (this.parent && this.parent.nodeTypeId === NodeType.Root) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public toNodeInfo(): azdata.NodeInfo {
|
||||
return <azdata.NodeInfo>{
|
||||
nodePath: this.nodePath,
|
||||
nodeType: this.nodeTypeId,
|
||||
nodeSubType: this.nodeSubType,
|
||||
nodeStatus: this.nodeStatus,
|
||||
label: this.label,
|
||||
isLeaf: this.isAlwaysLeaf,
|
||||
metadata: this.metadata,
|
||||
errorMessage: this.errorStateMessage
|
||||
};
|
||||
}
|
||||
|
||||
public getChildren(): Thenable<TreeNode[]> {
|
||||
return this._objectExplorerCallbacks.getChildren(this);
|
||||
}
|
||||
|
||||
public isExpanded(): Thenable<boolean> {
|
||||
return this._objectExplorerCallbacks.isExpanded(this);
|
||||
}
|
||||
|
||||
public setExpandedState(expandedState: TreeItemCollapsibleState): Thenable<void> {
|
||||
return this._objectExplorerCallbacks.setNodeExpandedState(this, expandedState);
|
||||
}
|
||||
|
||||
public setSelected(selected: boolean, clearOtherSelections?: boolean): Thenable<void> {
|
||||
return this._objectExplorerCallbacks.setNodeSelected(this, selected, clearOtherSelections);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { TreeNode } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
|
||||
export class TreeNodeContextKey implements IContextKey<TreeNode> {
|
||||
|
||||
static NodeType = new RawContextKey<string>('nodeType', undefined);
|
||||
static SubType = new RawContextKey<string>('nodeSubType', undefined);
|
||||
static Status = new RawContextKey<string>('nodeStatus', undefined);
|
||||
static TreeNode = new RawContextKey<TreeNode>('treeNode', undefined);
|
||||
static NodeLabel = new RawContextKey<string>('nodeLabel', undefined);
|
||||
|
||||
private _nodeTypeKey: IContextKey<string>;
|
||||
private _subTypeKey: IContextKey<string>;
|
||||
private _statusKey: IContextKey<string>;
|
||||
private _treeNodeKey: IContextKey<TreeNode>;
|
||||
private _nodeLabelKey: IContextKey<string>;
|
||||
|
||||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
this._nodeTypeKey = TreeNodeContextKey.NodeType.bindTo(contextKeyService);
|
||||
this._subTypeKey = TreeNodeContextKey.SubType.bindTo(contextKeyService);
|
||||
this._statusKey = TreeNodeContextKey.Status.bindTo(contextKeyService);
|
||||
this._treeNodeKey = TreeNodeContextKey.TreeNode.bindTo(contextKeyService);
|
||||
this._nodeLabelKey = TreeNodeContextKey.NodeLabel.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
set(value: TreeNode) {
|
||||
this._treeNodeKey.set(value);
|
||||
this._nodeTypeKey.set(value && value.nodeTypeId);
|
||||
this._subTypeKey.set(value && value.nodeSubType);
|
||||
this._statusKey.set(value && value.nodeStatus);
|
||||
this._nodeLabelKey.set(value && value.label);
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this._nodeTypeKey.reset();
|
||||
this._subTypeKey.reset();
|
||||
this._statusKey.reset();
|
||||
this._treeNodeKey.reset();
|
||||
this._nodeLabelKey.reset();
|
||||
}
|
||||
|
||||
public get(): TreeNode {
|
||||
return this._treeNodeKey.get();
|
||||
}
|
||||
}
|
||||
133
src/sql/workbench/parts/queryPlan/browser/topOperations.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { PlanXmlParser } from 'sql/workbench/parts/queryPlan/common/planXmlParser';
|
||||
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachTableStyler } from 'sql/platform/theme/common/styler';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
|
||||
const topOperationColumns: Array<Slick.Column<any>> = [
|
||||
{ name: localize('topOperations.operation', 'Operation'), field: 'operation', sortable: true },
|
||||
{ name: localize('topOperations.object', 'Object'), field: 'object', sortable: true },
|
||||
{ name: localize('topOperations.estCost', 'Est Cost'), field: 'estCost', sortable: true },
|
||||
{ name: localize('topOperations.estSubtreeCost', 'Est Subtree Cost'), field: 'estSubtreeCost', sortable: true },
|
||||
{ name: localize('topOperations.actualRows', 'Actual Rows'), field: 'actualRows', sortable: true },
|
||||
{ name: localize('topOperations.estRows', 'Est Rows'), field: 'estRows', sortable: true },
|
||||
{ name: localize('topOperations.actualExecutions', 'Actual Executions'), field: 'actualExecutions', sortable: true },
|
||||
{ name: localize('topOperations.estCPUCost', 'Est CPU Cost'), field: 'estCPUCost', sortable: true },
|
||||
{ name: localize('topOperations.estIOCost', 'Est IO Cost'), field: 'estIOCost', sortable: true },
|
||||
{ name: localize('topOperations.parallel', 'Parallel'), field: 'parallel', sortable: true },
|
||||
{ name: localize('topOperations.actualRebinds', 'Actual Rebinds'), field: 'actualRebinds', sortable: true },
|
||||
{ name: localize('topOperations.estRebinds', 'Est Rebinds'), field: 'estRebinds', sortable: true },
|
||||
{ name: localize('topOperations.actualRewinds', 'Actual Rewinds'), field: 'actualRewinds', sortable: true },
|
||||
{ name: localize('topOperations.estRewinds', 'Est Rewinds'), field: 'estRewinds', sortable: true },
|
||||
{ name: localize('topOperations.partitioned', 'Partitioned'), field: 'partitioned', sortable: true }
|
||||
];
|
||||
|
||||
export class TopOperationsState {
|
||||
xml: string;
|
||||
dispose() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class TopOperationsTab implements IPanelTab {
|
||||
public readonly title = localize('topOperationsTitle', 'Top Operations');
|
||||
public readonly identifier = 'TopOperationsTab';
|
||||
public readonly view: TopOperationsView;
|
||||
|
||||
constructor(@IInstantiationService instantiationService: IInstantiationService) {
|
||||
this.view = instantiationService.createInstance(TopOperationsView);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose(this.view);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.view.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export class TopOperationsView implements IPanelView {
|
||||
private _state: TopOperationsState;
|
||||
private table: Table<any>;
|
||||
private disposables: IDisposable[] = [];
|
||||
private container = document.createElement('div');
|
||||
private dataView = new TableDataView();
|
||||
|
||||
constructor(@IThemeService private themeService: IThemeService) {
|
||||
this.table = new Table(this.container, {
|
||||
columns: topOperationColumns,
|
||||
dataProvider: this.dataView,
|
||||
sorter: (args) => {
|
||||
this.dataView.sort(args);
|
||||
}
|
||||
});
|
||||
this.disposables.push(this.table);
|
||||
this.disposables.push(attachTableStyler(this.table, this.themeService));
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
container.appendChild(this.container);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
dispose(this.disposables);
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.table.layout(dimension);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.dataView.clear();
|
||||
}
|
||||
|
||||
public showPlan(xml: string) {
|
||||
this.state.xml = xml;
|
||||
this.dataView.clear();
|
||||
let parser = new PlanXmlParser(xml);
|
||||
let operations = parser.topOperations;
|
||||
let data = operations.map(i => {
|
||||
return {
|
||||
operation: i.title,
|
||||
object: i.indexObject.title,
|
||||
estCost: i.estimatedOperatorCost,
|
||||
estSubtreeCost: i.subtreeCost,
|
||||
actualRows: i.runtimeInfo.actualRows,
|
||||
estRows: i.estimateRows,
|
||||
actualExecutions: i.runtimeInfo.actualExecutions,
|
||||
estCPUCost: i.estimateCpu,
|
||||
estIOCost: i.estimateIo,
|
||||
parallel: i.parallel,
|
||||
actualRebinds: '',
|
||||
estRebinds: i.estimateRebinds,
|
||||
actualRewinds: '',
|
||||
estRewinds: i.estimateRewinds,
|
||||
partitioned: i.partitioned
|
||||
};
|
||||
});
|
||||
this.dataView.push(data);
|
||||
}
|
||||
|
||||
public set state(val: TopOperationsState) {
|
||||
this._state = val;
|
||||
if (this.state.xml) {
|
||||
this.showPlan(this.state.xml);
|
||||
}
|
||||
}
|
||||
|
||||
public get state(): TopOperationsState {
|
||||
return this._state;
|
||||
}
|
||||
}
|
||||
295
src/sql/workbench/parts/queryPlan/common/planXmlParser.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
class RunTimeInformation {
|
||||
runtimePerThreads: RuntimePerThread[];
|
||||
public get actualRows(): number {
|
||||
let total = 0;
|
||||
if (this.runtimePerThreads) {
|
||||
this.runtimePerThreads.forEach(element => {
|
||||
total += element.actualRow;
|
||||
});
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
public get actualExecutions(): number {
|
||||
let total = 0;
|
||||
if (this.runtimePerThreads) {
|
||||
this.runtimePerThreads.forEach(element => {
|
||||
total += element.actualExecutions;
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimePerThread {
|
||||
threadId: number;
|
||||
actualRow: number;
|
||||
actualExecutionMode: string;
|
||||
actualExecutions: number;
|
||||
}
|
||||
|
||||
class IndexObject {
|
||||
database: string;
|
||||
schema: string;
|
||||
table: string;
|
||||
index: string;
|
||||
indexKind: string;
|
||||
|
||||
public get title() {
|
||||
let title: string = '';
|
||||
if (this.database && this.schema && this.table) {
|
||||
title = `${this.database}.${this.schema}.${this.table}.${this.index}`;
|
||||
if (this.indexKind && this.indexKind !== '') {
|
||||
title += `(${this.indexKind})`;
|
||||
}
|
||||
}
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
||||
class PlanNode {
|
||||
root: PlanNode;
|
||||
subtreeCost: number;
|
||||
private childrenNodes: PlanNode[];
|
||||
parent: PlanNode;
|
||||
physicalOp: string;
|
||||
logicalOp: string;
|
||||
id: number;
|
||||
estimateRows: string;
|
||||
estimateIo: string;
|
||||
estimateCpu: string;
|
||||
parallel: boolean;
|
||||
partitioned: boolean;
|
||||
estimateRewinds: string;
|
||||
estimateRebinds: string;
|
||||
runtimeInfo: RunTimeInformation;
|
||||
indexObject: IndexObject;
|
||||
|
||||
public addChildren(children: PlanNode[]): void {
|
||||
if (children) {
|
||||
children.forEach(element => {
|
||||
element.parent = this;
|
||||
});
|
||||
}
|
||||
this.childrenNodes = children;
|
||||
}
|
||||
|
||||
public get totalSubTreeCost(): number {
|
||||
let total = this.subtreeCost;
|
||||
if (total === 0) {
|
||||
this.children.forEach(element => {
|
||||
total += element.subtreeCost;
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public get children(): PlanNode[] {
|
||||
return this.childrenNodes;
|
||||
}
|
||||
|
||||
public get cost(): number {
|
||||
let total = this.subtreeCost;
|
||||
if (this.children && total !== 0) {
|
||||
this.children.forEach(element => {
|
||||
total -= element.subtreeCost;
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public get relativeCost(): number {
|
||||
let overallCost = this.root.totalSubTreeCost;
|
||||
return overallCost > 0 ? this.cost / overallCost : 0;
|
||||
}
|
||||
|
||||
public get estimatedOperatorCost(): number {
|
||||
return Math.round(this.relativeCost * 100);
|
||||
}
|
||||
|
||||
public get estimatedSubtreeCost(): number {
|
||||
let total = this.estimatedOperatorCost;
|
||||
if (this.children) {
|
||||
this.children.forEach(element => {
|
||||
total += element.estimatedSubtreeCost;
|
||||
});
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
public get title(): string {
|
||||
if (this.physicalOp === this.logicalOp) {
|
||||
return this.physicalOp;
|
||||
} else {
|
||||
return `${this.physicalOp}(${this.logicalOp})`;
|
||||
}
|
||||
}
|
||||
|
||||
public get treeViewPrefix(): string {
|
||||
return this.parent === undefined ? '' : `${this.parent.treeViewPrefix}-----`;
|
||||
}
|
||||
|
||||
public get treeViewTitle(): string {
|
||||
return `${this.treeViewPrefix}${this.title}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class PlanXmlParser {
|
||||
parser: DOMParser = new DOMParser();
|
||||
doc: Document;
|
||||
planXml: string;
|
||||
root: PlanNode;
|
||||
|
||||
constructor(planXml: string) {
|
||||
|
||||
this.doc = this.parser.parseFromString(planXml, 'application/xml');
|
||||
this.planXml = planXml;
|
||||
let queryPlanNode = this.findChildren(this.doc.children[0], 'QueryPlan');
|
||||
if (queryPlanNode && queryPlanNode.length > 0) {
|
||||
this.root = new PlanNode();
|
||||
let ops = this.createPlanNodes(queryPlanNode[0], 'RelOp', this.root);
|
||||
|
||||
this.root.addChildren(ops);
|
||||
this.root.subtreeCost = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public get topOperations(): PlanNode[] {
|
||||
let operations: PlanNode[] = [];
|
||||
if (this.root && this.root.children) {
|
||||
operations = this.addOperationsToList(operations, this.root.children);
|
||||
operations.sort((a, b) => {
|
||||
if (a.estimatedOperatorCost > b.estimatedOperatorCost) {
|
||||
return -1;
|
||||
} else if (a.estimatedOperatorCost <= b.estimatedOperatorCost) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
return operations;
|
||||
}
|
||||
|
||||
public get toTreeViewList(): PlanNode[] {
|
||||
let operations: PlanNode[] = [];
|
||||
operations = this.addOperationsToList(operations, this.root.children);
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
private addOperationsToList(list: PlanNode[], nodes: PlanNode[]): PlanNode[] {
|
||||
list = list.concat(nodes);
|
||||
nodes.forEach(element => {
|
||||
list = this.addOperationsToList(list, element.children);
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
private findChildren(element: Element, elementName: string, untilNode: string = undefined): Element[] {
|
||||
let elements: Element[] = [];
|
||||
if (element === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (let index = 0; index < element.childNodes.length; index++) {
|
||||
if (element.childNodes[index].nodeName.toLocaleLowerCase() === elementName.toLocaleLowerCase()) {
|
||||
elements = elements.concat(element.children[index]);
|
||||
}
|
||||
}
|
||||
if (elements.length > 0) {
|
||||
return elements;
|
||||
}
|
||||
for (let index = 0; index < element.childNodes.length; index++) {
|
||||
if (untilNode && element.childNodes[index].nodeName === untilNode) {
|
||||
continue;
|
||||
}
|
||||
let result = this.findChildren(element.children[index], elementName, untilNode);
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private createPlanNodes(element: Element, elementName: string, root: PlanNode): PlanNode[] {
|
||||
let nodePlans: PlanNode[] = [];
|
||||
|
||||
let children = this.findChildren(element, elementName);
|
||||
if (children) {
|
||||
for (let index = 0; index < children.length; index++) {
|
||||
let childNode = children[index];
|
||||
|
||||
let planNode = this.convertToPlanNode(childNode);
|
||||
planNode.root = root;
|
||||
planNode.addChildren(this.createPlanNodes(childNode, elementName, root));
|
||||
planNode.runtimeInfo = new RunTimeInformation();
|
||||
planNode.indexObject = new IndexObject();
|
||||
|
||||
let runtimeInfoNodes = this.findChildren(childNode, 'RunTimeCountersPerThread');
|
||||
if (runtimeInfoNodes) {
|
||||
planNode.runtimeInfo.runtimePerThreads = runtimeInfoNodes.map(x => this.convertToRuntimeInfo(x));
|
||||
}
|
||||
|
||||
let objectNodes = this.findChildren(childNode, 'Object', 'RelOp');
|
||||
if (objectNodes && objectNodes.length > 0) {
|
||||
planNode.indexObject = this.convertToObject(objectNodes[0]);
|
||||
}
|
||||
nodePlans = nodePlans.concat(planNode);
|
||||
}
|
||||
}
|
||||
|
||||
return nodePlans;
|
||||
}
|
||||
|
||||
private convertToPlanNode(element: Element): PlanNode {
|
||||
let planNode = new PlanNode();
|
||||
planNode.id = this.findAttribute(element.attributes, 'NodeId');
|
||||
planNode.logicalOp = this.findAttribute(element.attributes, 'LogicalOp');
|
||||
planNode.physicalOp = this.findAttribute(element.attributes, 'PhysicalOp');
|
||||
planNode.subtreeCost = +this.findAttribute(element.attributes, 'EstimatedTotalSubtreeCost');
|
||||
planNode.estimateRows = this.findAttribute(element.attributes, 'EstimateRows');
|
||||
planNode.estimateCpu = this.findAttribute(element.attributes, 'EstimateCPU');
|
||||
planNode.estimateIo = this.findAttribute(element.attributes, 'EstimateIO');
|
||||
planNode.estimateRebinds = this.findAttribute(element.attributes, 'EstimateRebinds');
|
||||
planNode.estimateRewinds = this.findAttribute(element.attributes, 'EstimateRewinds');
|
||||
planNode.parallel = this.findAttribute(element.attributes, 'Parallel') === '1';
|
||||
planNode.partitioned = this.findAttribute(element.attributes, 'Partitioned') === '1';
|
||||
return planNode;
|
||||
}
|
||||
|
||||
private convertToRuntimeInfo(element: Element): RuntimePerThread {
|
||||
let runtimeNode = new RuntimePerThread();
|
||||
runtimeNode.actualExecutionMode = this.findAttribute(element.attributes, 'ActualExecutionMode');
|
||||
runtimeNode.actualExecutions = +this.findAttribute(element.attributes, 'ActualExecutions');
|
||||
runtimeNode.actualRow = +this.findAttribute(element.attributes, 'ActualRows');
|
||||
runtimeNode.threadId = +this.findAttribute(element.attributes, 'Thread');
|
||||
return runtimeNode;
|
||||
}
|
||||
|
||||
private convertToObject(element: Element): IndexObject {
|
||||
let objectNode = new IndexObject();
|
||||
objectNode.database = this.findAttribute(element.attributes, 'Database');
|
||||
objectNode.index = this.findAttribute(element.attributes, 'Index');
|
||||
objectNode.indexKind = this.findAttribute(element.attributes, 'IndexKind');
|
||||
objectNode.schema = this.findAttribute(element.attributes, 'Schema');
|
||||
objectNode.table = this.findAttribute(element.attributes, 'Table');
|
||||
return objectNode;
|
||||
}
|
||||
|
||||
private findAttribute(attributes: NamedNodeMap, attName: string): any {
|
||||
for (let index = 0; index < attributes.length; index++) {
|
||||
let attribute = attributes[index];
|
||||
if (attribute.name === attName) {
|
||||
return attribute.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
src/sql/workbench/parts/queryPlan/common/queryPlanInput.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { EditorInput, EditorModel } from 'vs/workbench/common/editor';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
|
||||
|
||||
export class QueryPlanInput extends EditorInput {
|
||||
|
||||
public static ID: string = 'workbench.editorinputs.queryplan';
|
||||
public static SCHEMA: string = 'queryplan';
|
||||
|
||||
private _uniqueSelector: string;
|
||||
|
||||
constructor(private _xml: string, private _uri: string, private _connection: ConnectionManagementInfo) {
|
||||
super();
|
||||
}
|
||||
|
||||
public setUniqueSelector(uniqueSelector: string): void {
|
||||
this._uniqueSelector = uniqueSelector;
|
||||
}
|
||||
|
||||
public getTypeId(): string {
|
||||
return UntitledEditorInput.ID;
|
||||
}
|
||||
|
||||
public getName(): string {
|
||||
return 'Query Plan';
|
||||
}
|
||||
|
||||
public get planXml(): string {
|
||||
return this._xml;
|
||||
}
|
||||
|
||||
public getUri(): string {
|
||||
return this._uri;
|
||||
}
|
||||
|
||||
public supportsSplitEditor(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getConnectionProfile(): IConnectionProfile {
|
||||
//return this._connection.connectionProfile;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public resolve(refresh?: boolean): Promise<EditorModel> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get hasInitialized(): boolean {
|
||||
return !!this._uniqueSelector;
|
||||
}
|
||||
|
||||
public get uniqueSelector(): string {
|
||||
return this._uniqueSelector;
|
||||
}
|
||||
|
||||
public getConnectionInfo(): ConnectionManagementInfo {
|
||||
return this._connection;
|
||||
}
|
||||
}
|
||||
218
src/sql/workbench/parts/queryPlan/electron-browser/media/qp.css
Normal file
@@ -0,0 +1,218 @@
|
||||
div.qp-node {
|
||||
margin: 2px;
|
||||
padding: 2px;
|
||||
border: 1px solid;
|
||||
}
|
||||
div.qp-statement-header {
|
||||
margin: 2px;
|
||||
padding: 2px;
|
||||
font-size: 12px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
div.qp-node,
|
||||
div.qp-tt {
|
||||
font-size: 11px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.qp-node>div {
|
||||
font-family: Monospace;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div[class|='qp-icon'] {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.qp-tt {
|
||||
top: 4em;
|
||||
left: 2em;
|
||||
border: 1px solid;
|
||||
padding: 2px;
|
||||
width: 30em;
|
||||
}
|
||||
|
||||
.qp-tt div,
|
||||
.qp-tt table {
|
||||
font-family: Sans-Serif;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.qp-tt table {
|
||||
border-width: 0px;
|
||||
border-spacing: 0px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.qp-tt td,
|
||||
.qp-tt th {
|
||||
font-size: 11px;
|
||||
border-bottom: solid 1px;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.qp-tt td {
|
||||
text-align: right;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.qp-tt th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.qp-bold,
|
||||
.qp-tt-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.qp-tt-header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Icons */
|
||||
.qp-icon-Catchall{background: url('qp_icons.png') -96px -0px }
|
||||
.qp-icon-ArithmeticExpression{background: url('qp_icons.png') -0px -0px }
|
||||
.qp-icon-Assert{background: url('qp_icons.png') -32px -0px }
|
||||
.qp-icon-Assign{background: url('qp_icons.png') -64px -0px }
|
||||
.qp-icon-Bitmap{background: url('qp_icons.png') -96px -0px }
|
||||
.qp-icon-BookmarkLookup{background: url('qp_icons.png') -128px -0px }
|
||||
.qp-icon-ClusteredIndexDelete{background: url('qp_icons.png') -160px -0px }
|
||||
.qp-icon-ClusteredIndexInsert{background: url('qp_icons.png') -192px -0px }
|
||||
.qp-icon-ClusteredIndexScan{background: url('qp_icons.png') -224px -0px }
|
||||
.qp-icon-ClusteredIndexSeek{background: url('qp_icons.png') -256px -0px }
|
||||
.qp-icon-KeyLookup{background: url('qp_icons.png') -256px -0px }
|
||||
.qp-icon-ClusteredIndexUpdate{background: url('qp_icons.png') -288px -0px }
|
||||
.qp-icon-Collapse{background: url('qp_icons.png') -0px -32px }
|
||||
.qp-icon-ComputeScalar{background: url('qp_icons.png') -32px -32px }
|
||||
.qp-icon-Concatenation{background: url('qp_icons.png') -64px -32px }
|
||||
.qp-icon-ConstantScan{background: url('qp_icons.png') -96px -32px }
|
||||
.qp-icon-Convert{background: url('qp_icons.png') -128px -32px }
|
||||
.qp-icon-CursorCatchall{background: url('qp_icons.png') -96px -0px }
|
||||
.qp-icon-Declare{background: url('qp_icons.png') -160px -32px }
|
||||
.qp-icon-Delete{background: url('qp_icons.png') -288px -160px }
|
||||
.qp-icon-DistributeStreams{background: url('qp_icons.png') -224px -32px }
|
||||
.qp-icon-Dynamic{background: url('qp_icons.png') -256px -32px }
|
||||
.qp-icon-EagerSpool{background: url('qp_icons.png') -192px -160px }
|
||||
.qp-icon-FetchQuery{background: url('qp_icons.png') -288px -32px }
|
||||
.qp-icon-Filter{background: url('qp_icons.png') -0px -64px }
|
||||
.qp-icon-GatherStreams{background: url('qp_icons.png') -32px -64px }
|
||||
.qp-icon-HashMatch{background: url('qp_icons.png') -64px -64px }
|
||||
.qp-icon-HashMatchRoot{background: url('qp_icons.png') -64px -64px }
|
||||
.qp-icon-HashMatchTeam{background: url('qp_icons.png') -64px -64px }
|
||||
.qp-icon-If{background: url('qp_icons.png') -96px -64px }
|
||||
.qp-icon-Insert{background: url('qp_icons.png') -0px -192px }
|
||||
.qp-icon-InsertedScan{background: url('qp_icons.png') -128px -64px }
|
||||
.qp-icon-Intrinsic{background: url('qp_icons.png') -160px -64px }
|
||||
.qp-icon-IteratorCatchall{background: url('qp_icons.png') -96px -0px }
|
||||
.qp-icon-Keyset{background: url('qp_icons.png') -192px -64px }
|
||||
.qp-icon-LanguageElementCatchall{background: url('qp_icons.png') -96px -0px }
|
||||
.qp-icon-LazySpool{background: url('qp_icons.png') -192px -160px }
|
||||
.qp-icon-LogRowScan{background: url('qp_icons.png') -224px -64px }
|
||||
.qp-icon-MergeInterval{background: url('qp_icons.png') -256px -64px }
|
||||
.qp-icon-MergeJoin{background: url('qp_icons.png') -288px -64px }
|
||||
.qp-icon-NestedLoops{background: url('qp_icons.png') -0px -96px }
|
||||
.qp-icon-NonclusteredIndexDelete{background: url('qp_icons.png') -32px -96px }
|
||||
.qp-icon-NonclusteredIndexInsert{background: url('qp_icons.png') -64px -96px }
|
||||
.qp-icon-IndexScan{background: url('qp_icons.png') -96px -96px }
|
||||
.qp-icon-IndexSeek{background: url('qp_icons.png') -128px -96px }
|
||||
.qp-icon-NonclusteredIndexSpool{background: url('qp_icons.png') -160px -96px }
|
||||
.qp-icon-NonclusteredIndexUpdate{background: url('qp_icons.png') -192px -96px }
|
||||
.qp-icon-OnlineIndexInsert{background: url('qp_icons.png') -224px -96px }
|
||||
.qp-icon-ParameterTableScan{background: url('qp_icons.png') -256px -96px }
|
||||
.qp-icon-PopulationQuery{background: url('qp_icons.png') -288px -96px }
|
||||
.qp-icon-RdiLookup{background: url('qp_icons.png') -0px -128px }
|
||||
.qp-icon-RefreshQuery{background: url('qp_icons.png') -32px -128px }
|
||||
.qp-icon-RemoteDelete{background: url('qp_icons.png') -64px -128px }
|
||||
.qp-icon-RemoteInsert{background: url('qp_icons.png') -96px -128px }
|
||||
.qp-icon-RemoteQuery{background: url('qp_icons.png') -128px -128px }
|
||||
.qp-icon-RemoteScan{background: url('qp_icons.png') -160px -128px }
|
||||
.qp-icon-RemoteUpdate{background: url('qp_icons.png') -192px -128px }
|
||||
.qp-icon-RepartitionStreams{background: url('qp_icons.png') -224px -128px }
|
||||
.qp-icon-Result{background: url('qp_icons.png') -256px -128px }
|
||||
.qp-icon-RowCountSpool{background: url('qp_icons.png') -288px -128px }
|
||||
.qp-icon-Segment{background: url('qp_icons.png') -0px -160px }
|
||||
.qp-icon-Sequence{background: url('qp_icons.png') -32px -160px }
|
||||
.qp-icon-Sequenceproject{background: url('qp_icons.png') -64px -160px }
|
||||
.qp-icon-Snapshot{background: url('qp_icons.png') -96px -160px }
|
||||
.qp-icon-Sort{background: url('qp_icons.png') -128px -160px }
|
||||
.qp-icon-Split{background: url('qp_icons.png') -160px -160px }
|
||||
.qp-icon-Spool{background: url('qp_icons.png') -192px -160px }
|
||||
.qp-icon-Statement{background: url('qp_icons.png') -256px -128px }
|
||||
.qp-icon-StreamAggregate{background: url('qp_icons.png') -224px -160px }
|
||||
.qp-icon-Switch{background: url('qp_icons.png') -256px -160px }
|
||||
.qp-icon-TableDelete{background: url('qp_icons.png') -288px -160px }
|
||||
.qp-icon-TableInsert{background: url('qp_icons.png') -0px -192px }
|
||||
.qp-icon-TableScan{background: url('qp_icons.png') -32px -192px }
|
||||
.qp-icon-TableSpool{background: url('qp_icons.png') -64px -192px }
|
||||
.qp-icon-TableUpdate{background: url('qp_icons.png') -96px -192px }
|
||||
.qp-icon-TableValuedFunction{background: url('qp_icons.png') -128px -192px }
|
||||
.qp-icon-Top{background: url('qp_icons.png') -160px -192px }
|
||||
.qp-icon-Udx{background: url('qp_icons.png') -192px -192px }
|
||||
.qp-icon-Update{background: url('qp_icons.png') -96px -192px }
|
||||
.qp-icon-While{background: url('qp_icons.png') -224px -192px }
|
||||
.qp-icon-PopulateQuery{background: url('qp_icons.png') -96px -0px }
|
||||
.qp-icon-StmtCursor{background: url('qp_icons.png') -96px -0px }
|
||||
.qp-icon-SequenceProject{background: url('qp_icons.png') -96px -0px }
|
||||
.qp-icon-FastForward{background: url('qp_icons.png') -96px -0px }
|
||||
.qp-icon-SnapShot{background: url('qp_icons.png') -96px -0px }
|
||||
|
||||
/* Layout - can't touch this */
|
||||
.qp-tt {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
white-space: normal;
|
||||
-webkit-transition-delay: 0.5s;
|
||||
transition-delay: 0.5s;
|
||||
}
|
||||
|
||||
div.qp-node .qp-tt,
|
||||
.qp-noCssTooltip div.qp-node:hover .qp-tt {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
div.qp-node:hover .qp-tt {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.qp-tt table {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.qp-node {
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.qp-tr {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.qp-tr>div {
|
||||
display: table-cell;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.qp-root {
|
||||
display: table;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.qp-root svg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 0;
|
||||
background: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
After Width: | Height: | Size: 62 KiB |
@@ -0,0 +1,70 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/qp';
|
||||
|
||||
import { ElementRef, Component, Inject, forwardRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import * as QP from 'html-query-plan';
|
||||
|
||||
import { IBootstrapParams } from 'sql/platform/bootstrap/node/bootstrapService';
|
||||
import { IQueryPlanParams } from 'sql/platform/bootstrap/node/bootstrapParams';
|
||||
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { registerThemingParticipant, ICssStyleCollector, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import * as colors from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
export const QUERYPLAN_SELECTOR: string = 'queryplan-component';
|
||||
|
||||
@Component({
|
||||
selector: QUERYPLAN_SELECTOR,
|
||||
template: `
|
||||
<div #container class="fullsize" style="overflow: scroll">
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class QueryPlanComponent implements OnDestroy, OnInit {
|
||||
|
||||
private _planXml: string;
|
||||
private _disposables: Array<IDisposable> = [];
|
||||
@ViewChild('container', { read: ElementRef }) _container: ElementRef;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(IBootstrapParams) private _params: IQueryPlanParams
|
||||
) { }
|
||||
|
||||
ngOnDestroy() {
|
||||
dispose(this._disposables);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (this._params) {
|
||||
this.planXml = this._params.planXml;
|
||||
}
|
||||
this._disposables.push(registerThemingParticipant(this._updateTheme));
|
||||
}
|
||||
|
||||
public set planXml(val: string) {
|
||||
this._planXml = val;
|
||||
if (this._planXml) {
|
||||
QP.showPlan(this._container.nativeElement, this._planXml, {
|
||||
jsTooltips: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _updateTheme(theme: ITheme, collector: ICssStyleCollector) {
|
||||
let backgroundColor = theme.getColor(colors.editorBackground);
|
||||
let foregroundColor = theme.getColor(colors.editorForeground);
|
||||
|
||||
if (backgroundColor) {
|
||||
collector.addRule(`div.qp-node, .qp-tt, .qp-root { background-color: ${backgroundColor} }`);
|
||||
}
|
||||
|
||||
if (foregroundColor) {
|
||||
collector.addRule(`.qp-root { color: ${foregroundColor} }`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { NgModule, Inject, forwardRef, ApplicationRef, ComponentFactoryResolver, Type } from '@angular/core';
|
||||
import { APP_BASE_HREF, CommonModule } from '@angular/common';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
import { IBootstrapParams, ISelector, providerIterator } from 'sql/platform/bootstrap/node/bootstrapService';
|
||||
import { QueryPlanComponent } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlan.component';
|
||||
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
// Connection Dashboard main angular module
|
||||
export const QueryPlanModule = (params: IBootstrapParams, selector: string, instantiationService: IInstantiationService): Type<any> => {
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
QueryPlanComponent
|
||||
],
|
||||
entryComponents: [QueryPlanComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
BrowserModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: APP_BASE_HREF, useValue: '/' },
|
||||
{ provide: IBootstrapParams, useValue: params },
|
||||
{ provide: ISelector, useValue: selector },
|
||||
...providerIterator(instantiationService)
|
||||
]
|
||||
})
|
||||
class ModuleClass {
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
|
||||
@Inject(ISelector) private selector: string
|
||||
) {
|
||||
}
|
||||
|
||||
ngDoBootstrap(appRef: ApplicationRef) {
|
||||
const factory = this._resolver.resolveComponentFactory(QueryPlanComponent);
|
||||
(<any>factory).factory.selector = this.selector;
|
||||
appRef.bootstrap(factory);
|
||||
}
|
||||
}
|
||||
|
||||
return ModuleClass;
|
||||
};
|
||||
118
src/sql/workbench/parts/queryPlan/electron-browser/queryPlan.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as QP from 'html-query-plan';
|
||||
|
||||
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
|
||||
|
||||
import { Dimension, clearNode } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class QueryPlanState {
|
||||
xml: string;
|
||||
dispose() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryPlanTab implements IPanelTab {
|
||||
public readonly title = localize('queryPlanTitle', 'Query Plan');
|
||||
public readonly identifier = 'QueryPlanTab';
|
||||
public readonly view: QueryPlanView;
|
||||
|
||||
constructor() {
|
||||
this.view = new QueryPlanView();
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose(this.view);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.view.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryPlanView implements IPanelView {
|
||||
private qp: QueryPlan;
|
||||
private xml: string;
|
||||
private container = document.createElement('div');
|
||||
private _state: QueryPlanState;
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
container.appendChild(this.container);
|
||||
this.container.style.overflow = 'scroll';
|
||||
if (!this.qp) {
|
||||
this.qp = new QueryPlan(this.container);
|
||||
if (this.xml) {
|
||||
this.qp.xml = this.xml;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.container.remove();
|
||||
this.qp = undefined;
|
||||
this.container = undefined;
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.container.style.width = dimension.width + 'px';
|
||||
this.container.style.height = dimension.height + 'px';
|
||||
}
|
||||
|
||||
public clear() {
|
||||
if (this.qp) {
|
||||
this.qp.xml = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public showPlan(xml: string) {
|
||||
if (this.qp) {
|
||||
this.qp.xml = xml;
|
||||
} else {
|
||||
this.xml = xml;
|
||||
}
|
||||
if (this.state) {
|
||||
this.state.xml = xml;
|
||||
}
|
||||
}
|
||||
|
||||
public set state(val: QueryPlanState) {
|
||||
this._state = val;
|
||||
if (this.state.xml) {
|
||||
this.showPlan(this.state.xml);
|
||||
}
|
||||
}
|
||||
|
||||
public get state(): QueryPlanState {
|
||||
return this._state;
|
||||
}
|
||||
}
|
||||
|
||||
export class QueryPlan {
|
||||
private _xml: string;
|
||||
constructor(private container: HTMLElement) {
|
||||
}
|
||||
|
||||
public set xml(xml: string) {
|
||||
this._xml = xml;
|
||||
clearNode(this.container);
|
||||
if (this.xml) {
|
||||
QP.showPlan(this.container, this._xml, {
|
||||
jsTooltips: false
|
||||
});
|
||||
(<any>this.container.querySelectorAll('div.qp-tt')).forEach(toolTip => {
|
||||
toolTip.classList.add('monaco-editor');
|
||||
toolTip.classList.add('monaco-editor-hover');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get xml(): string {
|
||||
return this._xml;
|
||||
}
|
||||
}
|
||||
@@ -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 'vs/css!sql/parts/query/editor/media/queryEditor';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { EditorOptions } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
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 { QueryPlanInput } from 'sql/workbench/parts/queryPlan/common/queryPlanInput';
|
||||
import { QueryPlanModule } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlan.module';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IMetadataService } from 'sql/platform/metadata/common/metadataService';
|
||||
import { IScriptingService } from 'sql/platform/scripting/common/scriptingService';
|
||||
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||
import { bootstrapAngular } from 'sql/platform/bootstrap/node/bootstrapService';
|
||||
import { IQueryPlanParams } from 'sql/platform/bootstrap/node/bootstrapParams';
|
||||
import { QUERYPLAN_SELECTOR } from 'sql/workbench/parts/queryPlan/electron-browser/queryPlan.component';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
|
||||
export class QueryPlanEditor extends BaseEditor {
|
||||
|
||||
public static ID: string = 'workbench.editor.queryplan';
|
||||
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService,
|
||||
@IMetadataService private _metadataService: IMetadataService,
|
||||
@IScriptingService private _scriptingService: IScriptingService,
|
||||
@IQueryEditorService private _queryEditorService: IQueryEditorService,
|
||||
@IStorageService private storageService: IStorageService
|
||||
) {
|
||||
super(QueryPlanEditor.ID, telemetryService, themeService, storageService);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called to create the editor in the parent element.
|
||||
*/
|
||||
public createEditor(parent: HTMLElement): void {
|
||||
//Enable scrollbars when drawing area is larger than viewport
|
||||
parent.style.overflow = 'auto';
|
||||
//Set background of parent to white (same as .qp-root from src\sql\parts\grid\load\css\qp.css)
|
||||
//This is because the bottom-most tooltips can extend past the drawing area, which causes the
|
||||
//scrolling area to have gaps on the bottom and left. So if the colors aren't matched then
|
||||
//these gaps show up as different colors and look bad.
|
||||
//Another option would be to check the tooltip positions and reposition them if necessary
|
||||
//during the load - but changing the background color was the simplest and least error prone
|
||||
//(plus it's probable that we won't be using this control in the future anyways if development)
|
||||
//continues on the Query plan feature
|
||||
parent.style.background = '#fff';
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets focus on this editor. Specifically, it sets the focus on the hosted text editor.
|
||||
*/
|
||||
public focus(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the internal variable keeping track of the editor's size, and re-calculates the sash position.
|
||||
* To be called when the container of this editor changes size.
|
||||
*/
|
||||
public layout(dimension: DOM.Dimension): void {
|
||||
}
|
||||
|
||||
public setInput(input: QueryPlanInput, options: EditorOptions): Promise<void> {
|
||||
if (this.input instanceof QueryPlanInput && this.input.matches(input)) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
if (!input.hasInitialized) {
|
||||
this.bootstrapAngular(input);
|
||||
}
|
||||
this.revealElementWithTagName(input.uniqueSelector, this.getContainer());
|
||||
|
||||
return super.setInput(input, options, CancellationToken.None);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reveal the child element with the given tagName and hide all other elements.
|
||||
*/
|
||||
private revealElementWithTagName(tagName: string, parent: HTMLElement): void {
|
||||
let elementToReveal: HTMLElement;
|
||||
|
||||
for (let i = 0; i < parent.children.length; i++) {
|
||||
let child: HTMLElement = <HTMLElement>parent.children[i];
|
||||
if (child.tagName && child.tagName.toLowerCase() === tagName && !elementToReveal) {
|
||||
elementToReveal = child;
|
||||
} else {
|
||||
child.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
if (elementToReveal) {
|
||||
elementToReveal.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the angular components and record for this input that we have done so
|
||||
*/
|
||||
private bootstrapAngular(input: QueryPlanInput): void {
|
||||
// Get the bootstrap params and perform the bootstrap
|
||||
let params: IQueryPlanParams = {
|
||||
planXml: input.planXml
|
||||
};
|
||||
|
||||
let uniqueSelector = bootstrapAngular(this.instantiationService,
|
||||
QueryPlanModule,
|
||||
this.getContainer(),
|
||||
QUERYPLAN_SELECTOR,
|
||||
params);
|
||||
input.setUniqueSelector(uniqueSelector);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
|
||||
.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');
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/media/actionBarLabel';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { localize } from 'vs/nls';
|
||||
import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
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, TaskHistoryViewlet } from 'sql/workbench/parts/taskHistory/browser/taskHistoryViewlet';
|
||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
||||
import * as ext from 'vs/workbench/common/contributions';
|
||||
import { ITaskService } from 'sql/platform/taskHistory/common/taskService';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
|
||||
export class StatusUpdater implements ext.IWorkbenchContribution {
|
||||
static ID = 'data.taskhistory.statusUpdater';
|
||||
|
||||
private badgeHandle: lifecycle.IDisposable;
|
||||
private toDispose: lifecycle.IDisposable[];
|
||||
|
||||
constructor(
|
||||
@IActivityService private activityBarService: IActivityService,
|
||||
@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 = localize({ key: 'showTaskHistory', comment: ['Show Task History'] }, 'Show Task History');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(viewletDescriptor, layoutService, viewletService);
|
||||
}
|
||||
}
|
||||
|
||||
// Viewlet
|
||||
const viewletDescriptor = new ViewletDescriptor(
|
||||
TaskHistoryViewlet,
|
||||
VIEWLET_ID,
|
||||
'Task History',
|
||||
'taskHistoryViewlet',
|
||||
1
|
||||
);
|
||||
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor);
|
||||
|
||||
// Register StatusUpdater
|
||||
(<ext.IWorkbenchContributionsRegistry>Registry.as(ext.Extensions.Workbench)).registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored);
|
||||
|
||||
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 History',
|
||||
localize('taskHistory.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'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '3_views',
|
||||
command: {
|
||||
id: VIEWLET_ID,
|
||||
title: localize({ key: 'miViewTasks', comment: ['&& denotes a mnemonic'] }, "&&Tasks")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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/workbench/parts/taskHistory/common/taskNode';
|
||||
import { CancelAction, ScriptAction } from 'sql/workbench/parts/taskHistory/common/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): IAction[] {
|
||||
if (element instanceof TaskNode) {
|
||||
return this.getTaskHistoryActions(tree, element);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
public hasSecondaryActions(tree: ITree, element: any): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITree, ContextMenuEvent } from 'vs/base/parts/tree/browser/tree';
|
||||
import * as treedefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
||||
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/workbench/parts/taskHistory/browser/taskHistoryActionProvider';
|
||||
|
||||
/**
|
||||
* Extends the tree controller to handle clicks on the tree elements
|
||||
*/
|
||||
export class TaskHistoryController extends treedefaults.DefaultController {
|
||||
|
||||
constructor(
|
||||
private actionProvider: TaskHistoryActionProvider,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
|
||||
import { TaskNode } from 'sql/workbench/parts/taskHistory/common/taskNode';
|
||||
|
||||
/**
|
||||
* 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): Promise<any> {
|
||||
if (element instanceof TaskNode) {
|
||||
return Promise.resolve((<TaskNode>element).children);
|
||||
}
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the element's parent in a promise.
|
||||
*/
|
||||
public getParent(tree: ITree, element: any): Promise<any> {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
|
||||
import { TaskNode, TaskStatus } from 'sql/workbench/parts/taskHistory/common/taskNode';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import * as Utils from 'sql/platform/connection/common/utils';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export interface ITaskHistoryTemplateData {
|
||||
root: HTMLElement;
|
||||
icon: HTMLElement;
|
||||
title: HTMLSpanElement;
|
||||
description: HTMLSpanElement;
|
||||
time: HTMLSpanElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 sql 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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
177
src/sql/workbench/parts/taskHistory/browser/taskHistoryView.ts
Normal 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 * as errors from '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 { 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 { hide, $, append } from 'vs/base/browser/dom';
|
||||
|
||||
import { TaskHistoryRenderer } from 'sql/workbench/parts/taskHistory/browser/taskHistoryRenderer';
|
||||
import { TaskHistoryDataSource } from 'sql/workbench/parts/taskHistory/browser/taskHistoryDataSource';
|
||||
import { TaskHistoryController } from 'sql/workbench/parts/taskHistory/browser/taskHistoryController';
|
||||
import { TaskHistoryActionProvider } from 'sql/workbench/parts/taskHistory/browser/taskHistoryActionProvider';
|
||||
import { ITaskService } from 'sql/platform/taskHistory/common/taskService';
|
||||
import { TaskNode, TaskStatus } from 'sql/workbench/parts/taskHistory/common/taskNode';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
import { IExpandableTree } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
|
||||
/**
|
||||
* TaskHistoryView implements the dynamic tree view.
|
||||
*/
|
||||
export class TaskHistoryView {
|
||||
private _messages: HTMLElement;
|
||||
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 = append(container, $('div.empty-task-message'));
|
||||
|
||||
if (taskNode && taskNode.hasChildren) {
|
||||
hide(this._messages);
|
||||
}
|
||||
let noTaskMessage = localize('noTaskMessage', 'No task history to display. Try backup or restore task to view its execution status.');
|
||||
append(this._messages, $('span')).innerText = noTaskMessage;
|
||||
|
||||
this._tree = this.createTaskHistoryTree(container, this._instantiationService);
|
||||
this._toDispose.push(this._tree.onDidChangeSelection((event) => this.onSelected(event)));
|
||||
|
||||
// Theme styler
|
||||
this._toDispose.push(attachListStyler(this._tree, this._themeService));
|
||||
|
||||
this._toDispose.push(this._taskService.onAddNewTask(args => {
|
||||
hide(this._messages);
|
||||
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: localize({ key: 'taskHistory.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];
|
||||
}
|
||||
// convert to old VS Code tree interface with expandable methods
|
||||
let expandableTree: IExpandableTree = <IExpandableTree>this._tree;
|
||||
targetsToExpand = expandableTree.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, 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
import 'vs/css!./media/taskHistoryViewlet';
|
||||
import { Viewlet } from 'vs/workbench/browser/viewlet';
|
||||
import { toggleClass, Dimension } 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 { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { TaskHistoryView } from 'sql/workbench/parts/taskHistory/browser/taskHistoryView';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
|
||||
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,
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IStorageService storageService: IStorageService
|
||||
) {
|
||||
super(VIEWLET_ID, configurationService, layoutService, telemetryService, themeService, storageService);
|
||||
}
|
||||
|
||||
private onError(err: any): void {
|
||||
if (isPromiseCanceledError(err)) {
|
||||
return;
|
||||
}
|
||||
this._notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: err
|
||||
});
|
||||
}
|
||||
|
||||
public create(parent: HTMLElement): Promise<void> {
|
||||
super.create(parent);
|
||||
this._root = parent;
|
||||
this._taskHistoryView = this._instantiationService.createInstance(TaskHistoryView);
|
||||
this._taskHistoryView.renderBody(parent);
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
public setVisible(visible: boolean): void {
|
||||
super.setVisible(visible);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
68
src/sql/workbench/parts/taskHistory/common/taskAction.ts
Normal 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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { ITaskService } from 'sql/platform/taskHistory/common/taskService';
|
||||
import { TaskNode } from 'sql/workbench/parts/taskHistory/common/taskNode';
|
||||
import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
|
||||
export class CancelAction extends Action {
|
||||
public static ID = 'taskHistory.cancel';
|
||||
public static LABEL = localize('cancelTask.cancel', 'Cancel');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@ITaskService private _taskService: ITaskService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
public run(element: TaskNode): Promise<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 Promise.resolve(true);
|
||||
});
|
||||
}
|
||||
return Promise.resolve(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('taskAction.script', 'Script');
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQueryEditorService private _queryEditorService: IQueryEditorService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(element: TaskNode): Promise<boolean> {
|
||||
if (element instanceof TaskNode) {
|
||||
if (element.script && element.script !== '') {
|
||||
this._queryEditorService.newSqlEditor(element.script);
|
||||
}
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
115
src/sql/workbench/parts/taskHistory/common/taskNode.ts
Normal 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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
import { Modal } from 'sql/workbench/browser/modal/modal';
|
||||
import { IConnectionManagementService, INewConnectionParams } from 'sql/platform/connection/common/connectionManagement';
|
||||
import * as DialogHelper from 'sql/workbench/browser/modal/dialogHelper';
|
||||
import { TreeCreationUtils } from 'sql/parts/objectExplorer/viewlet/treeCreationUtils';
|
||||
import { TreeUpdateUtils, IExpandableTree } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
|
||||
import { TreeCreationUtils } from 'sql/workbench/parts/objectExplorer/browser/treeCreationUtils';
|
||||
import { TreeUpdateUtils, IExpandableTree } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { TabbedPanel, PanelTabIdentifier } from 'sql/base/browser/ui/panel/panel';
|
||||
import { RecentConnectionTreeController, RecentConnectionActionsProvider } from 'sql/workbench/parts/connection/browser/recentConnectionTreeController';
|
||||
|
||||
@@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { IExpandableTree } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
|
||||
import { IExpandableTree } from 'sql/workbench/parts/objectExplorer/browser/treeUpdateUtils';
|
||||
|
||||
/**
|
||||
* Implements tree view for file browser
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
|
||||
import { TreeNode, TreeItemCollapsibleState } from 'sql/parts/objectExplorer/common/treeNode';
|
||||
import { NodeType } from 'sql/workbench/parts/objectExplorer/common/nodeType';
|
||||
import { TreeNode, TreeItemCollapsibleState } from 'sql/workbench/parts/objectExplorer/common/treeNode';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -18,7 +18,7 @@ import * as TelemetryKeys from 'sql/platform/telemetry/telemetryKeys';
|
||||
import * as TelemetryUtils from 'sql/platform/telemetry/telemetryUtilities';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { warn, error } from 'sql/base/common/log';
|
||||
import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView';
|
||||
import { ServerTreeView } from 'sql/workbench/parts/objectExplorer/browser/serverTreeView';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import * as Utils from 'sql/platform/connection/common/utils';
|
||||
import { entries } from 'sql/base/common/objects';
|
||||
|
||||
@@ -8,7 +8,7 @@ import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
|
||||
import { IConnectableInput, IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { IQueryEditorService, IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService';
|
||||
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
||||
import { QueryPlanInput } from 'sql/workbench/parts/queryPlan/common/queryPlanInput';
|
||||
import { sqlModeId, untitledFilePrefix, getSupportedInputResource } from 'sql/parts/common/customInputConverter';
|
||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||
|
||||
|
||||
@@ -8,12 +8,12 @@ import Severity from 'vs/base/common/severity';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
import { SERVER_GROUP_CONFIG, SERVER_GROUP_COLORS_CONFIG } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroup.contribution';
|
||||
import { SERVER_GROUP_CONFIG, SERVER_GROUP_COLORS_CONFIG } from 'sql/workbench/parts/objectExplorer/common/serverGroup.contribution';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
import { IServerGroupController, IServerGroupDialogCallbacks } from 'sql/platform/serverGroup/common/serverGroupController';
|
||||
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ServerGroupDialog } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog';
|
||||
import { ServerGroupViewModel } from 'sql/parts/objectExplorer/serverGroupDialog/serverGroupViewModel';
|
||||
import { ServerGroupDialog } from 'sql/workbench/parts/objectExplorer/browser/serverGroupDialog';
|
||||
import { ServerGroupViewModel } from 'sql/workbench/parts/objectExplorer/common/serverGroupViewModel';
|
||||
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
|
||||
export class ServerGroupController implements IServerGroupController {
|
||||
|
||||