Compare commits

...

33 Commits

Author SHA1 Message Date
Kevin Cunnane
619c816e7f Fix flex container and card layout issues (#1195)
* Fix main layout issues and add box around card
- Need to improve box size as it's taking too much space

* Fix UI issues with flexContainer and cards

* Simplify card HTML
2018-04-23 09:54:44 -07:00
Karl Burtram
3d6fb7a8fa Update SQL Tools Service to 1.4.0-alpha.23 (#1202) 2018-04-22 22:26:30 -07:00
Kevin Cunnane
891624c085 Fix #1198 Extension gallery doesn't show dashboard contributions in Contributions tab (#1200)
- Showing dashboard.tabs and dashboard.insights in Contributions
- Decided not to show dashboard.containers for now since these are logically just parts of the tabs, and dont' have user-facing benefit.
2018-04-22 17:42:44 -07:00
Leila Lali
a7c4686980 Feature/input box component (#1190)
* Adding new view component for input box
2018-04-20 16:26:58 -07:00
Matt Irvine
93aa052856 Initial work for custom model view dialogs (#1183) 2018-04-19 16:24:02 -07:00
Anthony Dresser
61d05f6782 adds coloring for unfocused tables (#1182) 2018-04-19 12:22:37 -07:00
Anthony Dresser
60ccae48f1 add auto refresh interval to insights (#1173) 2018-04-19 11:10:44 -07:00
Abbie Petchtes
4b454581fd fix task icon in dashboard (#1174) 2018-04-18 15:49:46 -07:00
Anthony Dresser
f0aadebd2c fix chart viewer accessibility (#1164) 2018-04-18 13:44:33 -07:00
Leila Lali
d3f0ac7954 refactor model components to not reference any dashboard services (#1168)
* refactor model components to not reference any dashboard services
2018-04-18 11:48:20 -07:00
Aditya Bist
134f76c17f Feature/agent finishes (#1149)
* added icons for steps and fixed bugs in jobs view page

* put svgs in common folder

* added steps header logo

* added full path for import

* changed codes to text

* removed cat id, changed bools to yes/no and fixed steps theme

* localized the strings

* set the jobs table column widths

* added indicators for failure, unknown and canceled jobs in jobs view

* fixed jobs panel style and jobs view scrolling

* fixed jobs view page styling

* fixed job history tree size rows

* made error messages copy-able

* made job history tree work with keyboard
2018-04-17 16:45:22 -07:00
Karl Burtram
b9c877a109 Add a "Telemetry Opt-Out" menu item (#1172) 2018-04-17 14:15:56 -07:00
Kevin Cunnane
aa243a8aae ensure npm install copies typings (#1170)
- Important so that build of the extension "works" as expected
2018-04-17 13:48:35 -07:00
Abbie Petchtes
e5c1c6f544 fix regression in insight widget where the data is set twice (#1163) 2018-04-17 11:06:40 -07:00
Anthony Dresser
9e9f85079e fix up backup dialog for accessibility (#1155) 2018-04-16 17:30:11 -07:00
Abbie Petchtes
ae8e2e1f89 Add showTopNData option to insight charts (#1157)
* add showTopNData option to insight charts

* address comments

* fix the label in the widget when label first column is set
2018-04-16 14:30:24 -07:00
Kevin Cunnane
b2c70e9301 Feat/model backed ui (#1145)
This is an initial PR for a new model-driven UI where extensions can provide definitions of the components & how they're laid out using Containers.
#1140, #1141, #1142, #1143 and #1144 are all tracking additional work needed to improve the initial implementation and fix some issues with the implementation.

Features:
- Supports defining a FlexContainer that maps to a flexbox-based layout.
- Supports creating a card component, which is a key-value pair based control that will lay out simple information to a user. Eventually this will have an optional set of actions associated with it.
- Has a sample project which shows how to use the API and was used for verification
2018-04-13 15:59:18 -07:00
Anthony Dresser
e022f4a0d1 add title to back button (#1154) 2018-04-13 15:07:09 -07:00
Anthony Dresser
8bc32e6371 add accessibility for panel (#1138) 2018-04-13 12:09:36 -07:00
Anthony Dresser
f739c47984 Adds aria labels to all input ui (#1136)
* adds aria-label to inputs for connection

* formatting

* add ariaLabels to all checkboxes/inputboxes/dropdowns
2018-04-13 12:09:25 -07:00
Abbie Petchtes
13fb9fdfd2 Add keyboard shortcuts for focus on current query and go to next output query tab (#1153)
* add go to next output query tab shotcut

* add a new keyboard shortcut for focus on current query

* minor change
2018-04-13 11:11:25 -07:00
Abbie Petchtes
9a5f51bfbf Focus on the first focus-able when toggle result and message (#1147)
* when focus on the first focusable when toggle result and message

* minor change
2018-04-12 17:26:49 -07:00
Karl Burtram
06bab6a38c Add Privacy Statement and License menu items (#1148)
* Add Privacy Statement and License menu items

* Fix spaces
2018-04-12 17:24:21 -07:00
Karl Burtram
c35a14d8fd Fix incorrect source header from previous commit (#1146) 2018-04-12 15:25:23 -07:00
Karl Burtram
b914073147 Add missing double-quotes to insights schema description (#1139) 2018-04-12 15:00:47 -07:00
ChrisSun-ms
04ae18143b fix backup URI (#1137) 2018-04-12 13:13:05 -07:00
Anthony Dresser
efe8e81b6e Connection inputs labels (#1129)
* adds aria-label to inputs for connection

* formatting
2018-04-12 11:29:16 -07:00
Anthony Dresser
c33ddfabf9 add necessary attributes for accessibility on the results grid panes (#1126) 2018-04-12 11:29:02 -07:00
Aditya Bist
8ec5451e64 Feature/agent2 adbist (#1113)
* added caching for jobs view and history page

* added build fix

* removed agent from cache service name

* put cache in job mgmt service and fixed view destroy error

* jobs view cache and refresh implemented

* refresh and cache complete

* added refresh to job history page

* fixed steps style

* explicitly tell user when no steps are available

* show message when no prev runs available

* code review refactor

* cleaned code

* added code that got left out
2018-04-12 10:36:34 -07:00
Karl Burtram
20853ddf7e Bump SQL Ops to 0.28.4 (#1134) 2018-04-11 17:07:36 -07:00
Abbie Petchtes
091d4cb924 enable keyboard for explain button and Line1 link in message pane (#1132) 2018-04-11 16:50:41 -07:00
Karl Burtram
cd0210c88a Port VS Code telemetry opt-in dialog (#1130) 2018-04-11 15:47:34 -07:00
Anthony Dresser
ed10f984b6 refactored collapsing widget, fixed refreshing a collapsed widget (#1107) 2018-04-11 14:39:23 -07:00
190 changed files with 16048 additions and 994 deletions

3
.vscode/launch.json vendored
View File

@@ -89,7 +89,8 @@
"skipFiles": [
"**/winjs*.js"
],
"webRoot": "${workspaceFolder}"
"webRoot": "${workspaceFolder}",
"timeout": 15000
},
{
"type": "node",

View File

@@ -1,18 +1,18 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "1.4.0-alpha.20",
"version": "1.4.0-alpha.23",
"downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.0.zip",
"Windows_64": "win-x64-netcoreapp2.0.zip",
"OSX": "osx-x64-netcoreapp2.0.tar.gz",
"CentOS_7": "rhel-x64-netcoreapp2.0.tar.gz",
"Debian_8": "rhel-x64-netcoreapp2.0.tar.gz",
"Fedora_23": "rhel-x64-netcoreapp2.0.tar.gz",
"OpenSUSE_13_2": "rhel-x64-netcoreapp2.0.tar.gz",
"RHEL_7": "rhel-x64-netcoreapp2.0.tar.gz",
"SLES_12_2": "rhel-x64-netcoreapp2.0.tar.gz",
"Ubuntu_14": "rhel-x64-netcoreapp2.0.tar.gz",
"Ubuntu_16": "rhel-x64-netcoreapp2.0.tar.gz"
"Windows_86": "win-x86-netcoreapp2.1.zip",
"Windows_64": "win-x64-netcoreapp2.1.zip",
"OSX": "osx-x64-netcoreapp2.1.tar.gz",
"CentOS_7": "rhel-x64-netcoreapp2.1.tar.gz",
"Debian_8": "rhel-x64-netcoreapp2.1.tar.gz",
"Fedora_23": "rhel-x64-netcoreapp2.1.tar.gz",
"OpenSUSE_13_2": "rhel-x64-netcoreapp2.1.tar.gz",
"RHEL_7": "rhel-x64-netcoreapp2.1.tar.gz",
"SLES_12_2": "rhel-x64-netcoreapp2.1.tar.gz",
"Ubuntu_14": "rhel-x64-netcoreapp2.1.tar.gz",
"Ubuntu_16": "rhel-x64-netcoreapp2.1.tar.gz"
},
"installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
"executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"]

View File

@@ -1,6 +1,6 @@
{
"name": "sqlops",
"version": "0.28.3",
"version": "0.28.4",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"

View File

@@ -16,6 +16,8 @@
"darwinBundleIdentifier": "com.sqlopsstudio.oss",
"reportIssueUrl": "https://github.com/Microsoft/sqlopsstudio/issues/new?labels=customer%20reported%20issue",
"requestFeatureUrl": "https://github.com/Microsoft/sqlopsstudio/issues/new?labels=feature-request",
"privacyStatementUrl": "https://privacy.microsoft.com/en-us/privacystatement",
"telemetryOptOutUrl": "https://github.com/Microsoft/sqlopsstudio/wiki/How-to-Disable-Telemetry-Reporting",
"urlProtocol": "sqlops",
"enableTelemetry": true,
"aiConfig": {

3
samples/sqlservices/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules
*.vsix
typings/sqlops.proposed.d.ts

57
samples/sqlservices/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,57 @@
// A launch configuration that launches the extension inside a new window
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
// To debug the extension:
// 1. please install the "SQL Operations Studio Debug" extension into VSCode
// 2. Ensure sqlops is added to your path:
// - open SQL Operations Studio
// - run the command "Install 'sqlops' command in PATH"
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug in SqlOps install",
"type": "sqlopsExtensionHost",
"request": "launch",
"runtimeExecutable": "sqlops",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
]
},
{
"type": "node",
"request": "attach",
"name": "Attach to Ops Studio",
"protocol": "inspector",
"port": 5870,
"restart": true,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/out/**/*.js"
],
"preLaunchTask": "",
"timeout": 25000
},
{
"name": "Debug in enlistment",
"type": "sqlopsExtensionHost",
"request": "launch",
"windows": {
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.bat"
},
"osx": {
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
},
"linux": {
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
},
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"timeout": 20000
}
]
}

14
samples/sqlservices/.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"type": "gulp",
"task": "build",
"problemMatcher": [
"$gulp-tsc"
]
}
]
}

View File

@@ -0,0 +1,4 @@
.vscode/**
.vscode-test/**
.gitignore
vsc-extension-quickstart.md

View File

@@ -0,0 +1 @@
This is a sample extension that will show some basic model-backed UI scenarios. The long-term goal is to use SQL Service querying (e.g. see if Agent and other services are running) and visualize in interesting ways. Additional suggestions for improving this sample are welcome.

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
"use strict";
// NOTE: These are es6 gulpfiles
// Basic build tasks
require('./tasks/buildtasks');
// VSIX generation tasks
require('./tasks/packagetasks');

7118
samples/sqlservices/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,81 @@
{
"name": "sqlservices",
"displayName": "sqlservices",
"description": "Lists SQL Server service status in the management dashboard for a server",
"version": "0.0.1",
"publisher": "demo",
"engines": {
"vscode": "^1.21.0",
"sqlops": "*"
},
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"main": "./out/src/extension",
"contributes": {
"dashboard.tabs": [
{
"id": "sqlservices.tab",
"title": "sqlservices",
"icon": {
"light": "./out/src/media/insights.svg",
"dark": "./out/src/media/insights_inverse.svg"
},
"description": "Shows available services running in the SQL Server instance",
"container": {
"nav-section": [
{
"id": "sqlservices",
"title": "Services",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"container": {
"modelview-container": null
}
},
{
"id": "splitPanel",
"title": "SplitPanel",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"container": {
"modelview-container": null
}
}
]
}
}
]
},
"scripts": {
"build": "gulp build",
"compile": "gulp compile",
"watch": "gulp watch",
"typings": "gulp copytypings",
"postinstall": "node ./node_modules/vscode/bin/install && node ./node_modules/sqlops/bin/install && gulp copytypings"
},
"dependencies": {
"vscode-nls": "^3.2.2"
},
"devDependencies": {
"@types/node": "^7.0.43",
"child-process-promise": "^2.2.1",
"del": "^3.0.0",
"gulp": "^4.0.0",
"gulp-color": "0.0.1",
"gulp-sourcemaps": "^2.6.4",
"gulp-tslint": "^6.0.2",
"gulp-typescript": "^3.2.4",
"sqlops": "github:anthonydresser/sqlops-extension-sqlops",
"tslint": "^3.14.0",
"typescript": "^2.6.1",
"vscode": "^1.1.14"
}
}

View File

@@ -0,0 +1,9 @@
/*
* This file should contain SQL code that returns a result set
* To get started, build your own queries in SQL Operations Studio and click the
* "View as Chart" button to get the correct chart format. Then choose "Create Insight"
* and update the package.json with the JSON contents, and this file with the query
* used to generate the chart.
*/
select 'My Label' as [Label], 1 as [Value]

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
// CONFIG VALUES ///////////////////////////////////////////////////////////
export const extensionConfigSectionName = 'sqlservices';
export const configLogDebugInfo = 'logDebugInfo';

View File

@@ -0,0 +1,109 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
import * as Utils from '../utils';
import * as vscode from 'vscode';
import SplitPropertiesPanel from './splitPropertiesPanel';
/**
* The main controller class that initializes the extension
*/
export default class MainController implements vscode.Disposable {
constructor(protected context: vscode.ExtensionContext) {
}
// PUBLIC METHODS //////////////////////////////////////////////////////
public dispose(): void {
this.deactivate();
}
/**
* Deactivates the extension
*/
public deactivate(): void {
Utils.logDebug('Main controller deactivated');
}
public activate(): Promise<boolean> {
this.registerSqlServicesModelView();
this.registerSplitPanelModelView();
sqlops.tasks.registerTask('sqlservices.clickTask', (profile) => {
vscode.window.showInformationMessage(`Clicked from profile ${profile.serverName}.${profile.databaseName}`);
});
return Promise.resolve(true);
}
private registerSqlServicesModelView(): void {
sqlops.dashboard.registerModelViewProvider('sqlservices', async (view) => {
let flexModel = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
alignItems: 'center'
}).withItems([
// 1st child panel with N cards
view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'column',
alignItems: 'center',
justifyContent: 'center'
})
.withItems([
view.modelBuilder.card()
.withProperties<sqlops.CardProperties>({
label: 'label1',
value: 'value1',
actions: [{ label: 'action', taskId: 'sqlservices.clickTask' }]
})
.component()
]).component(),
// 2nd child panel with N cards
view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' })
.withItems([
view.modelBuilder.card()
.withProperties<sqlops.CardProperties>({
label: 'label2',
value: 'value2',
actions: [{ label: 'action', taskId: 'sqlservices.clickTask' }]
})
.component()
]).component()
], { flex: '1 1 50%' })
.component();
await view.initializeModel(flexModel);
});
}
private registerSplitPanelModelView(): void {
sqlops.dashboard.registerModelViewProvider('splitPanel', async (view) => {
let numPanels = 3;
let splitPanel = new SplitPropertiesPanel(view, numPanels);
await view.initializeModel(splitPanel.modelBase);
// Add a bunch of cards after an initial timeout
setTimeout(async () => {
for (let i = 0; i < 10; i++) {
let panel = i % numPanels;
let card = view.modelBuilder.card().component();
card.label = `label${i.toString()}`;
splitPanel.addItem(card, panel);
}
}, 0);
});
}
}

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
/**
* The main controller class that initializes the extension
*/
export default class SplitPropertiesPanel {
private panels: sqlops.FlexContainer[];
private _modelBase: sqlops.FlexContainer;
constructor(view: sqlops.ModelView, numPanels: number) {
this.panels = [];
let ratio = Math.round(100 / numPanels);
for (let i = 0; i < numPanels; i++) {
this.panels.push(view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'column' }).component());
}
this._modelBase = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row'
}).withItems(this.panels, {
flex: `0 1 ${ratio}%`
})
.component();
}
public get modelBase(): sqlops.Component {
return this._modelBase;
}
public addItem(item: sqlops.Component, panel: number): void {
if (panel >= this.panels.length) {
throw new Error(`Cannot add to panel ${panel} as only ${this.panels.length - 1} panels defined`);
}
this.panels[panel].addItem(item, undefined);
}
}

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import MainController from './controllers/mainController';
let mainController: MainController;
export function activate(context: vscode.ExtensionContext): Promise<boolean> {
let activations: Promise<boolean>[] = [];
// Start the main controller
mainController = new MainController(context);
context.subscriptions.push(mainController);
activations.push(mainController.activate());
return Promise.all(activations)
.then((results: boolean[]) => {
for (let result of results) {
if (!result) {
return false;
}
}
return true;
});
}
export function deactivate(): void {
if (mainController) {
mainController.deactivate();
}
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>insights</title><path class="cls-1" d="M15,4V8H14V5.71L9.49,10.2l-2-2L2,13.71V14H15v1H1V1H2V12.29L7.49,6.8l2,2L13.28,5H11V4Z"/></svg>

After

Width:  |  Height:  |  Size: 282 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>insights_inverse</title><path class="cls-1" d="M15,4V8H14V5.71L9.49,10.2l-2-2L2,13.71V14H15v1H1V1H2V12.29L7.49,6.8l2,2L13.28,5H11V4Z"/></svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs-extra';
import * as handlebars from 'handlebars';
import * as path from 'path';
import * as vscode from 'vscode';
import * as Constants from './constants';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
/**
* Helper to log messages to the developer console if enabled
* @param msg Message to log to the console
*/
export function logDebug(msg: any): void {
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
let logDebugInfo = config[Constants.configLogDebugInfo];
if (logDebugInfo === true) {
let currentTime = new Date().toLocaleTimeString();
let outputMsg = '[' + currentTime + ']: ' + msg ? msg.toString() : '';
console.log(outputMsg);
}
}
export function renderTemplateHtml(extensionPath: string, templateName: string, templateValues: object): Thenable<string> {
let templatePath = path.join(extensionPath, 'resources', templateName);
// 1) Read the template from the disk
// 2) Compile it as a handlebars template and render the HTML
// 3) On failure, return a simple string as an error
return fs.readFile(templatePath, 'utf-8')
.then(templateText => {
let template = handlebars.compile(templateText);
return template(templateValues);
})
.then(
undefined,
error => {
logDebug(error);
return localize('errorLoadingTab', 'An error occurred while loading the tab');
}
);
}

View File

@@ -0,0 +1,124 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
"use strict";
let del = require('del');
let gulp = require('gulp');
let srcmap = require('gulp-sourcemaps');
let tslint = require('gulp-tslint');
let ts = require('gulp-typescript');
let cproc = require('child_process');
let os = require('os');
let config = require('./config');
let tsProject = ts.createProject('tsconfig.json');
// GULP TASKS //////////////////////////////////////////////////////////////
gulp.task('clean', function(done) {
return del('out', done);
});
gulp.task('lint', () => {
return gulp.src([
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/test/**/*.ts'
])
.pipe((tslint({
formatter: "verbose"
})))
.pipe(tslint.report());
});
gulp.task('compile:src', function(done) {
gulp.src([
config.paths.project.root + '/src/**/*.sql',
config.paths.project.root + '/src/**/*.svg',
config.paths.project.root + '/src/**/*.html'
]).pipe(gulp.dest('out/src/'));
let srcFiles = [
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/src/**/*.js',
config.paths.project.root + '/typings/**/*.ts'
];
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function() {
if (process.env.BUILDMACHINE) {
done('Extension Tests failed to build. See Above.');
process.exit(1);
}
})
.pipe(srcmap.write('.', {
sourceRoot: function(file) {
return file.cwd + '/src';
}
}))
.pipe(gulp.dest('out/src/'));
});
gulp.task('compile:test', function(done) {
let srcFiles = [
config.paths.project.root + '/test/**/*.ts',
config.paths.project.root + '/typings/**/*.ts'
];
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function() {
if(process.env.BUILDMACHINE) {
done('Failed to compile test source, see above.');
process.exit(1);
}
})
.pipe(srcmap.write('.', {sourceRoot: function(file) { return file.cwd + '/test'; }}))
.pipe(gulp.dest('out/test/'));
});
// COMPOSED GULP TASKS /////////////////////////////////////////////////////
gulp.task("compile", gulp.series("compile:src", "compile:test"));
gulp.task("build", gulp.series("clean", "lint", "compile"));
gulp.task("watch", function() {
gulp.watch([config.paths.project.root + '/src/**/*',
config.paths.project.root + '/test/**/*.ts'],
gulp.series('build'));
});
gulp.task('test', (done) => {
let workspace = process.env['WORKSPACE'];
if (!workspace) {
workspace = process.cwd();
}
process.env.JUNIT_REPORT_PATH = workspace + '/test-reports/ext_xunit.xml';
let sqlopsPath = 'sqlops';
if (process.env['SQLOPS_DEV']) {
let suffix = os.platform === 'win32' ? 'bat' : 'sh';
sqlopsPath = `${process.env['SQLOPS_DEV']}/scripts/sql-cli.${suffix}`;
}
console.log(`Using SQLOPS Path of ${sqlopsPath}`);
cproc.exec(`${sqlopsPath} --extensionDevelopmentPath="${workspace}" --extensionTestsPath="${workspace}/out/test" --verbose`, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
process.exit(1);
}
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
done();
});
});
gulp.task('copytypings', function() {
return gulp.src(config.paths.project.root + '/../../src/sql/sqlops.proposed.d.ts')
.pipe(gulp.dest('typings/'));
});

View File

@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var path = require('path');
var projectRoot = path.resolve(path.dirname(__dirname));
var srcRoot = path.resolve(projectRoot, 'src');
var localization = path.resolve(projectRoot, 'localization');
var config = {
paths: {
project: {
root: projectRoot,
localization: localization
},
extension: {
root: srcRoot
}
}
};
module.exports = config;

View File

@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
let gulp = require('gulp');
let exec = require('child-process-promise').exec;
let color = require('gulp-color');
// CONSTANTS ///////////////////////////////////////////////////////////////
let packageVals = require('../package');
// HELPER FUNCTIONS ////////////////////////////////////////////////////////
let buildPackage = function(packageName) {
// Make sure there are
if (!packageVals.repository) {
return Promise.reject("Repository field is not defined in package.json");
}
// Initialize the package command with program and command
let vsceArgs = [];
vsceArgs.push('./node_modules/vsce/out/vsce');
vsceArgs.push('package'); // package command
// Add the package name
vsceArgs.push('-o');
vsceArgs.push(packageName);
// Put it all together and execute the command
let command = vsceArgs.join(' ');
console.log(command);
return exec(command);
};

View File

@@ -0,0 +1,16 @@
{
"compileOnSave": true,
"compilerOptions": {
"module": "commonjs",
"target": "ES6",
"lib": [ "es6" ],
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"rootDir": "."
},
"exclude": [
"node_modules",
".vscode-test"
]
}

View File

@@ -0,0 +1,123 @@
{
"rules": {
"align": [
true,
"parameters",
"statements"
],
"ban": false,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"tabs"
],
"interface-name": true,
"jsdoc-format": true,
"label-position": true,
"label-undefined": true,
"max-line-length": [
true,
160
],
"member-access": false,
"member-ordering": false,
"no-any": false,
"no-arg": true,
"no-bitwise": true,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-constructor-vars": false,
"no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": true,
"no-eval": true,
"no-inferrable-types": false,
"no-internal-module": true,
"no-null-keyword": true,
"no-require-imports": false,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-switch-case-fall-through": false,
"no-trailing-whitespace": true,
"no-unreachable": true,
"no-unused-expression": false,
"no-unused-variable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"no-var-requires": false,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-finally",
"check-whitespace"
],
"quotemark": [
true,
"single",
"avoid-escape"
],
"radix": true,
"semicolon": true,
"switch-default": true,
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef": [
true,
"call-signature",
"property-declaration"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"use-strict": false,
"variable-name": [
true,
"allow-leading-underscore",
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,7 @@ export interface ICheckboxOptions {
enabled?: boolean;
checked?: boolean;
onChange?: (val: boolean) => void;
ariaLabel?: string;
}
export class Checkbox extends Widget {
@@ -27,6 +28,10 @@ export class Checkbox extends Widget {
this._el = document.createElement('input');
this._el.type = 'checkbox';
if (opts.ariaLabel) {
this._el.setAttribute('aria-label', opts.ariaLabel);
}
this.onchange(this._el, e => {
this._onChange.fire(this.checked);
});

View File

@@ -49,6 +49,10 @@ export interface IDropdownOptions extends IDropdownStyles {
* Error Message to show if input is not part of the supplied list, only used if strictSelection = false
*/
errorMessage?: string;
/**
* Value to use as aria-label for the input box
*/
ariaLabel?: string;
}
export interface IDropdownStyles {
@@ -124,7 +128,8 @@ export class Dropdown extends Disposable {
validation: v => this._inputValidator(v)
},
placeholder: this._options.placeholder,
actions: [this._toggleAction]
actions: [this._toggleAction],
ariaLabel: this._options.ariaLabel
});
this._register(DOM.addDisposableListener(this._input.inputElement, DOM.EventType.FOCUS, () => {

View File

@@ -20,6 +20,7 @@ import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/cont
import { Button } from 'sql/base/browser/ui/button/button';
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
import * as TelemetryKeys from 'sql/common/telemetryKeys';
import { localize } from 'vs/nls';
export const MODAL_SHOWING_KEY = 'modalShowing';
export const MODAL_SHOWING_CONTEXT = new RawContextKey<Array<string>>(MODAL_SHOWING_KEY, []);
@@ -155,6 +156,7 @@ export abstract class Modal extends Disposable implements IThemable {
modalHeader.div({ class: 'modal-go-back' }, (cellContainer) => {
this._backButton = new Button(cellContainer);
this._backButton.icon = 'backButtonIcon';
this._backButton.title = localize('modalBack', "Back");
});
}
if (this._modalOptions.hasTitleIcon) {

View File

@@ -43,7 +43,8 @@ export function createOptionElement(option: sqlops.ServiceOption, rowContainer:
return null;
}
}
}
},
ariaLabel: option.displayName
});
optionWidget.value = optionValue;
inputElement = this.findElement(rowContainer, 'input');
@@ -55,7 +56,8 @@ export function createOptionElement(option: sqlops.ServiceOption, rowContainer:
optionWidget = new InputBox(rowContainer.getHTMLElement(), contextViewService, {
validationOptions: {
validation: (value: string) => (!value && option.isRequired) ? ({ type: MessageType.ERROR, content: option.displayName + missingErrorMessage }) : null
}
},
ariaLabel: option.displayName
});
optionWidget.value = optionValue;
if (option.valueType === ServiceOptionType.password) {

View File

@@ -49,7 +49,7 @@ let idPool = 0;
<div class="tabbedPanel fullsize" #tabbedPanel>
<div *ngIf="!options.showTabsWhenOne ? _tabs.length !== 1 : true" class="composite title" #titleContainer>
<div class="tabList" #tabList>
<div class="tabList" #tabList role="tablist">
<div *ngFor="let tab of _tabs">
<tab-header [tab]="tab" [showIcon]="options.showIcon" (onSelectTab)='selectTab($event)' (onCloseTab)='closeTab($event)'> </tab-header>
</div>
@@ -182,7 +182,7 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
if (input instanceof TabComponent) {
tab = input;
} else if (types.isNumber(input)) {
tab = this._tabs[input];
tab = this._tabs.toArray()[input];
} else if (types.isString(input)) {
tab = this._tabs.find(i => i.identifier === input);
}
@@ -226,6 +226,18 @@ export class PanelComponent extends Disposable implements AfterContentInit, OnIn
return this._activeTab.identifier;
}
/**
* Select on the next tab
*/
public selectOnNextTab(): void {
let activeIndex = this._tabs.toArray().findIndex(i => i === this._activeTab);
let nextTabIndex = activeIndex + 1;
if (nextTabIndex === this._tabs.length) {
nextTabIndex = 0;
}
this.selectTab(nextTabIndex);
}
private findAndRemoveTabFromMRU(tab: TabComponent): void {
let mruIndex = this._mru.findIndex(i => i === tab);

View File

@@ -58,6 +58,7 @@ export class TabbedPanel extends Disposable implements IThemable {
this.$parent.appendTo(container);
this.$header = $('.composite.title');
this.$tabList = $('.tabList');
this.$tabList.attr('role', 'tablist');
this.$tabList.style('height', this.headersize + 'px');
this.$header.append(this.$tabList);
let actionbarcontainer = $('.title-actions');
@@ -89,6 +90,9 @@ export class TabbedPanel extends Disposable implements IThemable {
private _createTab(tab: IInternalPanelTab): void {
let tabHeaderElement = $('.tab-header');
tabHeaderElement.attr('tabindex', '0');
tabHeaderElement.attr('role', 'tab');
tabHeaderElement.attr('aria-selected', 'false');
tabHeaderElement.attr('aria-label', tab.title);
let tabElement = $('.tab');
tabHeaderElement.append(tabElement);
let tabLabel = $('a.tabLabel');
@@ -114,7 +118,7 @@ export class TabbedPanel extends Disposable implements IThemable {
if (this._shownTab) {
this._tabMap.get(this._shownTab).label.removeClass('active');
this._tabMap.get(this._shownTab).header.removeClass('active');
this._tabMap.get(this._shownTab).header.removeClass('active').attr('aria-selected', 'false');
}
this._shownTab = id;
@@ -122,6 +126,7 @@ export class TabbedPanel extends Disposable implements IThemable {
let tab = this._tabMap.get(this._shownTab);
tab.label.addClass('active');
tab.header.addClass('active');
tab.header.attr('aria-selected', 'true');
tab.view.render(this.$body.getHTMLElement());
this._onTabChange.fire(id);
if (this._currentDimensions) {

View File

@@ -20,7 +20,7 @@ import { CloseTabAction } from './tabActions';
selector: 'tab-header',
template: `
<div #actionHeader class="tab-header" style="flex: 0 0; flex-direction: row;" [class.active]="tab.active" tabindex="0" (keyup)="onKey($event)">
<span class="tab" (click)="selectTab(tab)">
<span class="tab" (click)="selectTab(tab)" role="tab" [attr.aria-selected]="tab.active" [attr.aria-label]="tab.title">
<a class="tabLabel" [class.active]="tab.active" #tabLabel>
</a>
</span>

View File

@@ -26,7 +26,7 @@ export class RowDetailView {
this._options = mixin(options, this._defaults, false);
}
public init(grid: any) {
public init(grid: any): void {
this._grid = grid;
this._dataView = this._grid.getData();
@@ -61,17 +61,17 @@ export class RowDetailView {
this._options = $.extend(true, {}, this._options, options);
}
public handleClick(e: any, args: any) {
public handleClick(e: any, args: any): void {
// clicking on a row select checkbox
if (this._options.useRowClick || this._grid.getColumns()[args.cell].id === this._options.columnId && $(e.target).hasClass("detailView-toggle")) {
if (this._options.useRowClick || this._grid.getColumns()[args.cell].id === this._options.columnId && $(e.target).hasClass('detailView-toggle')) {
// if editing, try to commit
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
e.preventDefault();
e.stopImmediatePropagation();
return;
e.preventDefault();
e.stopImmediatePropagation();
return;
}
var item = this._dataView.getItem(args.row);
let item = this._dataView.getItem(args.row);
// trigger an event before toggling
this.onBeforeRowDetailToggle.notify({
@@ -100,36 +100,35 @@ export class RowDetailView {
// If we scroll save detail views that go out of cache range
public handleScroll(e, args) {
var range = this._grid.getRenderedRange();
let range = this._grid.getRenderedRange();
var start = (range.top > 0 ? range.top : 0);
var end = (range.bottom > this._dataView.getLength() ? range.bottom : this._dataView.getLength());
let start: number = (range.top > 0 ? range.top : 0);
let end: number = (range.bottom > this._dataView.getLength() ? range.bottom : this._dataView.getLength());
if (end <= 0) {
return;
}
// Get the item at the top of the view
var topMostItem = this._dataView.getItemByIdx(start);
let topMostItem = this._dataView.getItemByIdx(start);
// Check it is a parent item
if (topMostItem._parent === undefined)
{
if (topMostItem._parent === undefined) {
// This is a standard row as we have no parent.
var nextItem = this._dataView.getItemByIdx(start + 1);
if(nextItem !== undefined && nextItem._parent !== undefined)
{
let nextItem = this._dataView.getItemByIdx(start + 1);
if (nextItem !== undefined && nextItem._parent !== undefined) {
// This is likely the expanded Detail Row View
// Check for safety
if(nextItem._parent === topMostItem)
{
if (nextItem._parent === topMostItem) {
this.saveDetailView(topMostItem);
}
}
}
// Find the bottom most item that is likely to go off screen
var bottomMostItem = this._dataView.getItemByIdx(end - 1);
let bottomMostItem = this._dataView.getItemByIdx(end - 1);
// If we are a detailView and we are about to go out of cache view
if(bottomMostItem._parent !== undefined)
{
if (bottomMostItem._parent !== undefined) {
this.saveDetailView(bottomMostItem._parent);
}
}
@@ -143,17 +142,17 @@ export class RowDetailView {
// Collapse all of the open items
public collapseAll() {
for (var i = this._expandedRows.length - 1; i >= 0; i--) {
for (let i = this._expandedRows.length - 1; i >= 0; i--) {
this.collapseItem(this._expandedRows[i]);
}
}
// Saves the current state of the detail view
public saveDetailView(item) {
var view = $('#innerDetailView_' + item.id);
let view = $('#innerDetailView_' + item.id);
if (view) {
var html = $('#innerDetailView_' + item.id).html();
if(html !== undefined) {
let html = $('#innerDetailView_' + item.id).html();
if (html !== undefined) {
item._detailContent = html;
}
}
@@ -230,7 +229,7 @@ export class RowDetailView {
args.itemDetail._detailViewLoaded = true;
var idxParent = this._dataView.getIdxById(args.itemDetail.id);
let idxParent = this._dataView.getIdxById(args.itemDetail.id);
this._dataView.updateItem(args.itemDetail.id, args.itemDetail);
// trigger an event once the post template is finished loading
@@ -254,7 +253,7 @@ export class RowDetailView {
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
public getPaddingItem(parent, offset) {
var item: any = {};
let item: any = {};
for (let prop in this._grid.getData()) {
item[prop] = null;
@@ -284,7 +283,7 @@ export class RowDetailView {
item._height = (item._sizePadding * this._grid.getOptions().rowHeight);
let idxParent = this._dataView.getIdxById(item.id);
for (var idx = 1; idx <= item._sizePadding; idx++) {
for (let idx = 1; idx <= item._sizePadding; idx++) {
this._dataView.insertItem(idxParent + idx, this.getPaddingItem(item, idx));
}
}
@@ -307,12 +306,12 @@ export class RowDetailView {
public detailSelectionFormatter(row, cell, value, columnDef, dataContext) {
if (dataContext._collapsed === undefined) {
dataContext._collapsed = true,
dataContext._sizePadding = 0, //the required number of pading rows
dataContext._height = 0, //the actual height in pixels of the detail field
dataContext._isPadding = false,
dataContext._parent = undefined,
dataContext._offset = 0
dataContext._collapsed = true;
dataContext._sizePadding = 0; //the required number of pading rows
dataContext._height = 0; //the actual height in pixels of the detail field
dataContext._isPadding = false;
dataContext._parent = undefined;
dataContext._offset = 0;
}
if (dataContext._isPadding === true) {
@@ -320,9 +319,9 @@ export class RowDetailView {
} else if (dataContext._collapsed) {
return '<div class=\'detailView-toggle expand\'></div>';
} else {
var html = [];
var rowHeight = this._grid.getOptions().rowHeight;
var bottomMargin = 5;
let html = [];
let rowHeight = this._grid.getOptions().rowHeight;
let bottomMargin = 5;
//V313HAX:
//putting in an extra closing div after the closing toggle div and ommiting a
@@ -339,7 +338,7 @@ export class RowDetailView {
html.push("style='height:", dataContext._height, "px;"); //set total height of padding
html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
html.push("<div id='innerDetailView_" , dataContext.id , "'>" , dataContext._detailContent, "</div></div>");
html.push("<div id='innerDetailView_", dataContext.id, "'>", dataContext._detailContent, "</div></div>");
//&omit a final closing detail container </div> that would come next
return html.join('');
@@ -351,33 +350,32 @@ export class RowDetailView {
if (!item) return;
// Grad each of the dom items
var mainContainer = document.getElementById('detailViewContainer_' + item.id);
var cellItem = document.getElementById('cellDetailView_' + item.id);
var inner = document.getElementById('innerDetailView_' + item.id);
let mainContainer = document.getElementById('detailViewContainer_' + item.id);
let cellItem = document.getElementById('cellDetailView_' + item.id);
let inner = document.getElementById('innerDetailView_' + item.id);
if (!mainContainer || !cellItem || !inner) return;
for (var idx = 1; idx <= item._sizePadding; idx++) {
for (let idx = 1; idx <= item._sizePadding; idx++) {
this._dataView.deleteItem(item.id + "." + idx);
}
var rowHeight = this._grid.getOptions().rowHeight; // height of a row
var lineHeight = 13; //we know cuz we wrote the custom css innit ;)
let rowHeight = this._grid.getOptions().rowHeight; // height of a row
let lineHeight = 13; //we know cuz we wrote the custom css innit ;)
// Get the inner Item height as this will be the actual size
var itemHeight = inner.clientHeight;
let itemHeight = inner.clientHeight;
// Now work out how many rows
var rowCount = Math.ceil(itemHeight / rowHeight) + 1;
let rowCount = Math.ceil(itemHeight / rowHeight) + 1;
item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight);
item._height = (item._sizePadding * rowHeight);
// If the padding is now more than the original minRowBuff we need to increase it
if (this._grid.getOptions().minRowBuffer < item._sizePadding)
{
if (this._grid.getOptions().minRowBuffer < item._sizePadding) {
// Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
this._grid.getOptions().minRowBuffer =item._sizePadding + 3;
this._grid.getOptions().minRowBuffer = item._sizePadding + 3;
}
mainContainer.setAttribute("style", "max-height: " + item._height + "px");
@@ -386,7 +384,7 @@ export class RowDetailView {
}
let idxParent = this._dataView.getIdxById(item.id);
for (var idx = 1; idx <= item._sizePadding; idx++) {
for (let idx = 1; idx <= item._sizePadding; idx++) {
this._dataView.insertItem(idxParent + idx, this.getPaddingItem(item, idx));
}
}

View File

@@ -14,6 +14,7 @@ import { mixin } from 'vs/base/common/objects';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Dimension } from 'vs/base/browser/builder';
import { Orientation } from 'vs/base/browser/ui/splitview/splitview';
import { Widget } from 'vs/base/browser/ui/widget';
export interface ITableStyles extends IListStyles {
tableHeaderBackground?: Color;
@@ -27,18 +28,22 @@ function getDefaultOptions<T>(): Slick.GridOptions<T> {
};
}
export class Table<T extends Slick.SlickData> implements IThemable {
export class Table<T extends Slick.SlickData> extends Widget implements IThemable {
private styleElement: HTMLStyleElement;
private idPrefix: string;
private _grid: Slick.Grid<T>;
private _columns: Slick.Column<T>[];
private _data: TableDataView<T>;
private _styleElement: HTMLStyleElement;
private _idPrefix: string;
private _autoscroll: boolean;
private _onRowCountChangeListener: IDisposable;
private _container: HTMLElement;
private _tableContainer: HTMLElement;
private _classChangeTimeout: number;
constructor(parent: HTMLElement, data?: Array<T> | TableDataView<T>, columns?: Slick.Column<T>[], options?: Slick.GridOptions<T>) {
super();
if (data instanceof TableDataView) {
this._data = data;
} else {
@@ -55,13 +60,28 @@ export class Table<T extends Slick.SlickData> implements IThemable {
this._container = document.createElement('div');
this._container.className = 'monaco-table';
this._register(DOM.addDisposableListener(this._container, DOM.EventType.FOCUS, () => {
clearTimeout(this._classChangeTimeout);
this._classChangeTimeout = setTimeout(() => {
DOM.addClass(this._container, 'focused');
}, 100);
}, true));
this._register(DOM.addDisposableListener(this._container, DOM.EventType.BLUR, () => {
clearTimeout(this._classChangeTimeout);
this._classChangeTimeout = setTimeout(() => {
DOM.removeClass(this._container, 'focused');
}, 100);
}, true));
parent.appendChild(this._container);
this._styleElement = DOM.createStyleSheet(this._container);
this.styleElement = DOM.createStyleSheet(this._container);
this._tableContainer = document.createElement('div');
this._container.appendChild(this._tableContainer);
this._styleElement = DOM.createStyleSheet(this._container);
this.styleElement = DOM.createStyleSheet(this._container);
this._grid = new Slick.Grid<T>(this._tableContainer, this._data, this._columns, newOptions);
this._idPrefix = this._tableContainer.classList[0];
this.idPrefix = this._tableContainer.classList[0];
DOM.addClass(this._container, this.idPrefix);
this._onRowCountChangeListener = this._data.onRowCountChange(() => this._handleRowCountChange());
this._grid.onSort.subscribe((e, args) => {
this._data.sort(args);
@@ -200,78 +220,77 @@ export class Table<T extends Slick.SlickData> implements IThemable {
const content: string[] = [];
if (styles.tableHeaderBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-header .slick-header-column { background-color: ${styles.tableHeaderBackground}; }`);
content.push(`.monaco-table .${this.idPrefix} .slick-header .slick-header-column { background-color: ${styles.tableHeaderBackground}; }`);
}
if (styles.tableHeaderForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-header .slick-header-column { color: ${styles.tableHeaderForeground}; }`);
content.push(`.monaco-table .${this.idPrefix} .slick-header .slick-header-column { color: ${styles.tableHeaderForeground}; }`);
}
if (styles.listFocusBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .active { background-color: ${styles.listFocusBackground}; }`);
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .active { background-color: ${styles.listFocusBackground}; }`);
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .active:hover { background-color: ${styles.listFocusBackground}; }`); // overwrite :hover style in this case!
}
if (styles.listFocusForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .active { color: ${styles.listFocusForeground}; }`);
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .active { color: ${styles.listFocusForeground}; }`);
}
if (styles.listActiveSelectionBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected { background-color: ${styles.listActiveSelectionBackground}; }`);
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected:hover { background-color: ${styles.listActiveSelectionBackground}; }`); // overwrite :hover style in this case!
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .selected { background-color: ${styles.listActiveSelectionBackground}; }`);
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .selected:hover { background-color: ${styles.listActiveSelectionBackground}; }`); // overwrite :hover style in this case!
}
if (styles.listActiveSelectionForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected { color: ${styles.listActiveSelectionForeground}; }`);
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .selected { color: ${styles.listActiveSelectionForeground}; }`);
}
if (styles.listFocusAndSelectionBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected.active { background-color: ${styles.listFocusAndSelectionBackground}; }`);
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .selected.active { background-color: ${styles.listFocusAndSelectionBackground}; }`);
}
if (styles.listFocusAndSelectionForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected.active { color: ${styles.listFocusAndSelectionForeground}; }`);
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .selected.active { color: ${styles.listFocusAndSelectionForeground}; }`);
}
/* Commented out andresse 8/17/2017; keeping for reference as we iterate on the table styling */
// if (styles.listInactiveFocusBackground) {
// content.push(`.monaco-table .${this._idPrefix} .slick-row.focused { background-color: ${styles.listInactiveFocusBackground}; }`);
// content.push(`.monaco-table .${this._idPrefix} .slick-row.focused:hover { background-color: ${styles.listInactiveFocusBackground}; }`); // overwrite :hover style in this case!
// }
if (styles.listInactiveFocusBackground) {
content.push(`.monaco-table.${this.idPrefix} .slick-row .selected.active { background-color: ${styles.listInactiveFocusBackground}; }`);
content.push(`.monaco-table.${this.idPrefix} .slick-row .selected.active:hover { background-color: ${styles.listInactiveFocusBackground}; }`); // overwrite :hover style in this case!
}
// if (styles.listInactiveSelectionBackground) {
// content.push(`.monaco-table .${this._idPrefix} .slick-row .selected { background-color: ${styles.listInactiveSelectionBackground}; }`);
// content.push(`.monaco-table .${this._idPrefix} .slick-row .selected:hover { background-color: ${styles.listInactiveSelectionBackground}; }`); // overwrite :hover style in this case!
// }
if (styles.listInactiveSelectionBackground) {
content.push(`.monaco-table.${this.idPrefix} .slick-row .selected { background-color: ${styles.listInactiveSelectionBackground}; }`);
content.push(`.monaco-table.${this.idPrefix} .slick-row .selected:hover { background-color: ${styles.listInactiveSelectionBackground}; }`); // overwrite :hover style in this case!
}
// if (styles.listInactiveSelectionForeground) {
// content.push(`.monaco-table .${this._idPrefix} .slick-row .selected { color: ${styles.listInactiveSelectionForeground}; }`);
// }
if (styles.listInactiveSelectionForeground) {
content.push(`.monaco-table.${this.idPrefix} .slick-row .selected { color: ${styles.listInactiveSelectionForeground}; }`);
}
if (styles.listHoverBackground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row:hover { background-color: ${styles.listHoverBackground}; }`);
content.push(`.monaco-table.${this.idPrefix} .slick-row:hover { background-color: ${styles.listHoverBackground}; }`);
}
if (styles.listHoverForeground) {
content.push(`.monaco-table .${this._idPrefix} .slick-row:hover { color: ${styles.listHoverForeground}; }`);
content.push(`.monaco-table.${this.idPrefix} .slick-row:hover { color: ${styles.listHoverForeground}; }`);
}
if (styles.listSelectionOutline) {
content.push(`.monaco-table .${this._idPrefix} .slick-row .selected.active { outline: 1px dotted ${styles.listSelectionOutline}; outline-offset: -1px; }`);
content.push(`.monaco-table.${this.idPrefix} .slick-row .selected.active { outline: 1px dotted ${styles.listSelectionOutline}; outline-offset: -1px; }`);
}
/* Commented out andresse 8/17/2017; keeping for reference as we iterate on the table styling */
// if (styles.listFocusOutline) {
// content.push(`.monaco-table .${this._idPrefix}:focus .slick-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`);
// }
if (styles.listFocusOutline) {
content.push(`.monaco-table.${this.idPrefix}.focused .slick-row .selected { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; }`);
}
// if (styles.listInactiveFocusOutline) {
// content.push(`.monaco-table .${this._idPrefix} .slick-row.focused { outline: 1px dotted ${styles.listInactiveFocusOutline}; outline-offset: -1px; }`);
// }
if (styles.listInactiveFocusOutline) {
content.push(`.monaco-table.${this.idPrefix} .slick-row .selected .active { outline: 1px dotted ${styles.listInactiveFocusOutline}; outline-offset: -1px; }`);
}
if (styles.listHoverOutline) {
content.push(`.monaco-table .${this._idPrefix} .slick-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`);
content.push(`.monaco-table.${this.idPrefix} .slick-row:hover { outline: 1px dashed ${styles.listHoverOutline}; outline-offset: -1px; }`);
}
this._styleElement.innerHTML = content.join('\n');
this.styleElement.innerHTML = content.join('\n');
}
}

View File

@@ -153,12 +153,18 @@ export class ActionBar extends ActionRunner implements IActionRunner {
}
private updateFocusedItem(): void {
let actionIndex = 0;
for (let i = 0; i < this._actionsList.children.length; i++) {
let elem = this._actionsList.children[i];
if (DOM.isAncestor(document.activeElement, elem)) {
this._focusedItem = i;
this._focusedItem = actionIndex;
break;
}
if (elem.classList.contains('action-item')) {
actionIndex++;
}
}
}

View File

@@ -104,7 +104,9 @@ export class AutoOAuthDialog extends Modal {
});
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
inputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService);
inputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, {
ariaLabel: label
});
});
});
return inputBox;

View File

@@ -34,6 +34,11 @@ import * as TelemetryKeys from 'sql/common/telemetryKeys';
// in case that other non-Azure sign in is to be used
const firewallHelpUri = 'https://aka.ms/sqlopsfirewallhelp';
const LocalizedStrings = {
FROM: localize('from', 'From'),
TO: localize('to', 'To')
};
export class FirewallRuleDialog extends Modal {
public viewModel: FirewallRuleViewModel;
private _createButton: Button;
@@ -140,19 +145,23 @@ export class FirewallRuleDialog extends Modal {
subnetIPRangeSection = subnetIPRangeContainer.getHTMLElement();
subnetIPRangeContainer.div({ 'class': 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(localize('from', 'From'));
labelContainer.innerHtml(LocalizedStrings.FROM);
});
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
this._fromRangeinputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService);
this._fromRangeinputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, {
ariaLabel: LocalizedStrings.FROM
});
});
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(localize('to', 'To'));
labelContainer.innerHtml(LocalizedStrings.TO);
});
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
this._toRangeinputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService);
this._toRangeinputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, {
ariaLabel: LocalizedStrings.TO
});
});
});
});

View File

@@ -128,6 +128,7 @@ export class ConnectionWidget {
validationOptions: {
validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: serverNameOption.displayName + errorMessage }) : null
},
ariaLabel: serverNameOption.displayName
});
if (this._optionsMaps[ConnectionOptionSpecialType.authType]) {
@@ -141,12 +142,13 @@ export class ConnectionWidget {
this._userNameInputBox = new InputBox(userNameBuilder.getHTMLElement(), this._contextViewService, {
validationOptions: {
validation: (value: string) => self.validateUsername(value, userNameOption.isRequired) ? ({ type: MessageType.ERROR, content: userNameOption.displayName + errorMessage }) : null
}
},
ariaLabel: userNameOption.displayName
});
let passwordOption = this._optionsMaps[ConnectionOptionSpecialType.password];
let passwordBuilder = DialogHelper.appendRow(this._tableContainer, passwordOption.displayName, 'connection-label', 'connection-input');
this._passwordInputBox = new InputBox(passwordBuilder.getHTMLElement(), this._contextViewService);
this._passwordInputBox = new InputBox(passwordBuilder.getHTMLElement(), this._contextViewService, { ariaLabel: passwordOption.displayName });
this._passwordInputBox.inputElement.type = 'password';
this._password = '';
@@ -158,9 +160,10 @@ export class ConnectionWidget {
this._databaseNameInputBox = new Dropdown(databaseNameBuilder.getHTMLElement(), this._contextViewService, this._themeService, {
values: [this._defaultDatabaseName, this._loadingDatabaseName],
strictSelection : false,
strictSelection: false,
placeholder: this._defaultDatabaseName,
maxHeight: 125
maxHeight: 125,
ariaLabel: databaseOption.displayName
});
let serverGroupLabel = localize('serverGroup', 'Server group');
@@ -204,7 +207,7 @@ export class ConnectionWidget {
container.element('tr', {}, (rowContainer) => {
rowContainer.element('td');
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
checkbox = new Checkbox(inputCellContainer.getHTMLElement(), { label, checked: isChecked });
checkbox = new Checkbox(inputCellContainer.getHTMLElement(), { label, checked: isChecked, ariaLabel: label });
});
});
return checkbox;

View File

@@ -210,7 +210,6 @@ export class CollapseWidgetAction extends Action {
private _uri: string,
private _widgetUuid: string,
private collpasedState: boolean,
private collapsedStateChangedEvent: Event<boolean>,
@IAngularEventingService private _angularEventService: IAngularEventingService
) {
super(
@@ -218,7 +217,6 @@ export class CollapseWidgetAction extends Action {
collpasedState ? CollapseWidgetAction.EXPAND_LABEL : CollapseWidgetAction.COLLPASE_LABEL,
collpasedState ? CollapseWidgetAction.EXPAND_ICON : CollapseWidgetAction.COLLAPSE_ICON
);
this.collapsedStateChangedEvent(collapsed => this._updateState(collapsed));
}
run(): TPromise<boolean> {
@@ -232,8 +230,15 @@ export class CollapseWidgetAction extends Action {
}
private _updateState(collapsed: boolean): void {
if (collapsed === this.collpasedState) {
return;
}
this.collpasedState = collapsed;
this._setClass(this.collpasedState ? CollapseWidgetAction.EXPAND_ICON : CollapseWidgetAction.COLLAPSE_ICON);
this._setLabel(this.collpasedState ? CollapseWidgetAction.EXPAND_LABEL : CollapseWidgetAction.COLLPASE_LABEL);
}
public set state(collapsed: boolean) {
this._updateState(collapsed);
}
}

View File

@@ -14,6 +14,7 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar
import { WIDGETS_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
import { GRID_CONTAINER } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.contribution';
import { MODELVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardModelViewContainer.contribution';
import { CONTROLHOST_CONTAINER } from 'sql/parts/dashboard/containers/dashboardControlHostContainer.contribution';
import { NAV_SECTION } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution';
import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions, IDashboardContainer, registerContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
@@ -25,6 +26,7 @@ const containerTypes = [
WIDGETS_CONTAINER,
GRID_CONTAINER,
WEBVIEW_CONTAINER,
MODELVIEW_CONTAINER,
CONTROLHOST_CONTAINER,
NAV_SECTION
];

View File

@@ -13,6 +13,8 @@
</dashboard-webview-container>
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
</dashboard-widget-container>
<dashboard-modelview-container *ngIf="getContentType(tab) === 'modelview-container'" [tab]="tab">
</dashboard-modelview-container>
<dashboard-controlhost-container *ngIf="getContentType(tab) === 'controlhost-container'" [tab]="tab">
</dashboard-controlhost-container>
<dashboard-nav-section *ngIf="getContentType(tab) === 'nav-section'" [tab]="tab">

View File

@@ -9,6 +9,7 @@ import 'sql/parts/dashboard/common/dashboardPanelStyles';
import { Component, Inject, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef } from '@angular/core';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { WidgetConfig, TabConfig, TabSettingConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWidgetWrapper.component';
@@ -85,13 +86,15 @@ export abstract class DashboardPage extends AngularDisposable {
protected abstract propertiesWidget: WidgetConfig;
protected abstract get context(): string;
protected dashboardService: DashboardServiceInterface;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) protected dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) protected commonService: CommonServiceInterface,
@Inject(forwardRef(() => ElementRef)) protected _el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) protected _cd: ChangeDetectorRef
) {
super();
this.dashboardService = commonService as DashboardServiceInterface;
}
protected init() {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./dashboardControlHostContainer';
import { Component, forwardRef, Input, AfterContentInit, ViewChild } from '@angular/core';
import { Component, forwardRef, Input, AfterContentInit, ViewChild, OnChanges } from '@angular/core';
import Event, { Emitter } from 'vs/base/common/event';
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
@@ -18,6 +18,7 @@ import { ControlHostContent } from 'sql/parts/dashboard/contents/controlHostCont
</controlhost-content>
`
})
export class DashboardControlHostContainer extends DashboardTab implements AfterContentInit {
@Input() private tab: TabConfig;
@@ -53,6 +54,6 @@ export class DashboardControlHostContainer extends DashboardTab implements After
}
public refresh(): void {
// no op
this._hostContent.refresh();
}
}

View File

@@ -39,7 +39,7 @@ export class DashboardErrorContainer extends DashboardTab implements AfterViewIn
ngAfterViewInit() {
let errorMessage = this._errorMessageContainer.nativeElement as HTMLElement;
errorMessage.innerHTML = nls.localize('dashboardNavSection_loadTabError', 'The {0} has an invalid content. Please contact extension owner.', this.tab.title);
errorMessage.innerHTML = nls.localize('dashboardNavSection_loadTabError', 'The "{0}" section has invalid content. Please contact extension owner.', this.tab.title);
}
public get id(): string {

View File

@@ -9,6 +9,7 @@ import { Component, Inject, Input, forwardRef, ViewChild, ElementRef, ViewChildr
import { NgGridConfig, NgGrid, NgGridItem } from 'angular2-grid';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { TabConfig, WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWidgetWrapper.component';
import { subscriptionToDisposable } from 'sql/base/common/lifecycle';
@@ -153,7 +154,7 @@ export class DashboardGridContainer extends DashboardTab implements OnDestroy {
@ViewChildren(DashboardWidgetWrapper) private _widgets: QueryList<DashboardWidgetWrapper>;
@ViewChildren(WebviewContent) private _webViews: QueryList<WebviewContent>;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) protected dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) protected dashboardService: CommonServiceInterface,
@Inject(forwardRef(() => ElementRef)) protected _el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) protected _cd: ChangeDetectorRef
) {

View File

@@ -11,6 +11,7 @@ import { DashboardWidgetContainer } from 'sql/parts/dashboard/containers/dashboa
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { AngularEventType } from '../../../services/angularEventing/angularEventingService';
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWidgetWrapper.component';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
@@ -30,12 +31,15 @@ export class DashboardHomeContainer extends DashboardWidgetContainer {
@Input() private properties: WidgetConfig;
@ViewChild('propertiesClass') private _propertiesClass: DashboardWidgetWrapper;
private dashboardService: DashboardServiceInterface;
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) _cd: ChangeDetectorRef,
@Inject(forwardRef(() => DashboardServiceInterface)) protected dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) protected commonService: CommonServiceInterface,
) {
super(_cd);
this.dashboardService = commonService as DashboardServiceInterface;
}
ngAfterContentInit() {

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./dashboardWebviewContainer';
import { Component, forwardRef, Input, AfterContentInit, ViewChild } from '@angular/core';
import Event, { Emitter } from 'vs/base/common/event';
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
@Component({
selector: 'dashboard-modelview-container',
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardModelViewContainer) }],
template: `
<modelview-content [modelViewId]="tab.id">
</modelview-content>
`
})
export class DashboardModelViewContainer extends DashboardTab implements AfterContentInit {
@Input() private tab: TabConfig;
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
constructor() {
super();
}
ngAfterContentInit(): void {
this._register(this._modelViewContent.onResize(() => {
this._onResize.fire();
}));
}
public layout(): void {
this._modelViewContent.layout();
}
public get id(): string {
return this.tab.id;
}
public get editable(): boolean {
return this.tab.editable;
}
public refresh(): void {
// no op
}
}

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as nls from 'vs/nls';
import { registerContainerType, registerNavSectionContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
export const MODELVIEW_CONTAINER = 'modelview-container';
let modelviewSchema: IJSONSchema = {
type: 'null',
description: nls.localize('dashboard.container.modelview', "The model-backed view that will be displayed in this tab."),
default: null
};
registerContainerType(MODELVIEW_CONTAINER, modelviewSchema);
registerNavSectionContainerType(MODELVIEW_CONTAINER, modelviewSchema);

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
dashboard-modelview-container {
height: 100%;
width : 100%;
display: block;
}

View File

@@ -11,6 +11,8 @@
</dashboard-webview-container>
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
</dashboard-widget-container>
<dashboard-modelview-container *ngIf="getContentType(tab) === 'modelview-container'" [tab]="tab">
</dashboard-modelview-container>
<dashboard-grid-container *ngIf="getContentType(tab) === 'grid-container'" [tab]="tab">
</dashboard-grid-container>
<dashboard-error-container *ngIf="getContentType(tab) === 'error-container'" [tab]="tab">

View File

@@ -8,6 +8,7 @@ import 'vs/css!./dashboardNavSection';
import { Component, Inject, Input, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef, EventEmitter, OnChanges, AfterContentInit } from '@angular/core';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { WidgetConfig, TabConfig, NavSectionConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
import { TabComponent } from 'sql/base/browser/ui/panel/tab.component';
@@ -53,7 +54,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
@ViewChildren(DashboardTab) private _tabs: QueryList<DashboardTab>;
@ViewChild(PanelComponent) private _panel: PanelComponent;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) protected dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) protected dashboardService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) protected _cd: ChangeDetectorRef
) {
super();

View File

@@ -5,5 +5,4 @@
*--------------------------------------------------------------------------------------------*/
-->
<agentview-component *ngIf="(controlType) === 'agent'">
</agentview-component>
<agentview-component #agent *ngIf="(controlType) === 'agent'"></agentview-component>

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./controlHostContent';
import { Component, forwardRef, Input, OnInit, Inject, ChangeDetectorRef, ElementRef } from '@angular/core';
import { Component, forwardRef, Input, OnInit, Inject, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import Event, { Emitter } from 'vs/base/common/event';
import { Parts } from 'vs/workbench/services/part/common/partService';
@@ -13,15 +13,17 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import * as sqlops from 'sqlops';
import { memoize } from 'vs/base/common/decorators';
import { AgentViewComponent } from '../../jobManagement/agent/agentView.component';
@Component({
templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/contents/controlHostContent.component.html')),
selector: 'controlhost-content'
})
export class ControlHostContent implements OnInit {
export class ControlHostContent {
@Input() private webviewId: string;
private _onResize = new Emitter<void>();
@@ -32,16 +34,16 @@ export class ControlHostContent implements OnInit {
private _onMessageDisposable: IDisposable;
private _type: string;
/* Children components */
@ViewChild('agent') private _agentViewComponent: AgentViewComponent;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) private _dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef
) {
}
ngOnInit() {
}
public layout(): void {
}
@@ -73,4 +75,8 @@ export class ControlHostContent implements OnInit {
public get controlType(): string {
return this._type;
}
public refresh() {
this._agentViewComponent.refresh = true;
}
}

View File

@@ -25,6 +25,7 @@ import { InsightsWidget } from 'sql/parts/dashboard/widgets/insights/insightsWid
import { WebviewWidget } from 'sql/parts/dashboard/widgets/webview/webviewWidget.component';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
@@ -53,6 +54,7 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
@Input() private _config: WidgetConfig;
@Input() private collapsable = false;
private _collapseAction: CollapseWidgetAction;
private _collapsed = false;
public get collapsed(): boolean {
@@ -64,17 +66,13 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
return;
}
this._collapsed = val;
if (this.collapsedStateChangedEmitter) {
this.collapsedStateChangedEmitter.fire(this._collapsed);
}
this._collapseAction.state = val;
this._changeref.detectChanges();
if (!val) {
this.loadWidget();
}
}
private collapsedStateChangedEmitter: Emitter<boolean>;
@memoize
public get guid(): string {
return generateUuid();
@@ -91,7 +89,7 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver,
@Inject(forwardRef(() => ElementRef)) private _ref: ElementRef,
@Inject(forwardRef(() => DashboardServiceInterface)) private _bootstrap: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeref: ChangeDetectorRef,
@Inject(forwardRef(() => Injector)) private _injector: Injector
) {
@@ -114,8 +112,8 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
this._actionbar = new ActionBar(this._actionbarRef.nativeElement);
if (this._actions) {
if (this.collapsable) {
this.collapsedStateChangedEmitter = new Emitter<boolean>();
this._actionbar.push(this._bootstrap.instantiationService.createInstance(CollapseWidgetAction, this._bootstrap.getUnderlyingUri(), this.guid, this.collapsed, this.collapsedStateChangedEmitter.event), { icon: true, label: false });
this._collapseAction = this._bootstrap.instantiationService.createInstance(CollapseWidgetAction, this._bootstrap.getUnderlyingUri(), this.guid, this.collapsed);
this._actionbar.push(this._collapseAction, { icon: true, label: false });
}
this._actionbar.push(this._bootstrap.instantiationService.createInstance(ToggleMoreWidgetAction, this._actions, this._component.actionsContext), { icon: true, label: false });
}
@@ -123,13 +121,13 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
}
public refresh(): void {
if (this._component && this._component.refresh) {
if (!this.collapsed && this._component && this._component.refresh) {
this._component.refresh();
}
}
public layout(): void {
if (this._component && this._component.layout) {
if (!this.collapsed && this._component && this._component.layout) {
this._component.layout();
}
}
@@ -170,7 +168,7 @@ export class DashboardWidgetWrapper extends AngularDisposable implements OnInit
this._component = componentRef.instance;
let actions = componentRef.instance.actions;
if (componentRef.instance.refresh) {
actions.push(new RefreshWidgetAction(componentRef.instance.refresh, componentRef.instance));
actions.push(new RefreshWidgetAction(this.refresh, this));
}
if (actions !== undefined && actions.length > 0) {
this._actions = actions;

View File

@@ -16,7 +16,8 @@ import { memoize } from 'vs/base/common/decorators';
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { IDashboardWebview } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { IDashboardWebview } from 'sql/services/dashboard/common/dashboardViewService';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import * as sqlops from 'sqlops';
@@ -36,17 +37,19 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo
private _onMessageDisposable: IDisposable;
private _webview: Webview;
private _html: string;
private _dashboardService: DashboardServiceInterface;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) private _dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) private commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef
) {
super();
this._dashboardService = commonService as DashboardServiceInterface;
}
ngOnInit() {
this._dashboardService.dashboardWebviewService.registerWebview(this);
this._dashboardService.dashboardViewService.registerWebview(this);
this._createWebview();
this._register(addDisposableListener(window, EventType.RESIZE, e => {
this.layout();

View File

@@ -9,6 +9,7 @@ import { Component, Inject, Input, forwardRef, ViewChild, ViewChildren, QueryLis
import { NgGridConfig, NgGrid, NgGridItem } from 'angular2-grid';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWidgetWrapper.component';
import { subscriptionToDisposable, AngularDisposable } from 'sql/base/common/lifecycle';
@@ -111,11 +112,14 @@ export class WidgetContent extends AngularDisposable implements AfterViewInit {
@ViewChildren(NgGridItem) private _items: QueryList<NgGridItem>;
@ViewChild('scrollable', { read: ElementRef }) private _scrollable: ElementRef;
@ViewChild('scrollContainer', { read: ElementRef }) private _scrollContainer: ElementRef;
protected dashboardService: DashboardServiceInterface;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) protected dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) protected commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) protected _cd: ChangeDetectorRef
) {
super();
this.dashboardService = commonService as DashboardServiceInterface;
}
ngAfterViewInit() {

View File

@@ -9,6 +9,7 @@ import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, O
import { Router } from '@angular/router';
import { DashboardServiceInterface } from './services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import * as Utils from 'sql/parts/connection/common/utils';
import { RefreshWidgetAction, EditDashboardAction } from 'sql/parts/dashboard/common/actions';
@@ -36,7 +37,7 @@ export class DashboardComponent extends AngularDisposable implements OnInit {
private editDisposable: IDisposable;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) private _bootstrapService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
@Inject(forwardRef(() => Router)) private _router: Router,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef
) {

View File

@@ -14,6 +14,7 @@ import { ChartsModule } from 'ng2-charts/ng2-charts';
import CustomUrlSerializer from 'sql/common/urlSerializer';
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
import { Extensions as ComponentExtensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
@@ -25,6 +26,7 @@ import * as TelemetryKeys from 'sql/common/telemetryKeys';
/* Services */
import { BreadcrumbService } from 'sql/parts/dashboard/services/breadcrumb.service';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
/* Directives */
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
@@ -35,9 +37,12 @@ import { DashboardWidgetWrapper } from 'sql/parts/dashboard/contents/dashboardWi
import { DashboardWidgetContainer } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.component';
import { DashboardGridContainer } from 'sql/parts/dashboard/containers/dashboardGridContainer.component';
import { DashboardWebviewContainer } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.component';
import { DashboardModelViewContainer } from 'sql/parts/dashboard/containers/dashboardModelViewContainer.component';
import { DashboardErrorContainer } from 'sql/parts/dashboard/containers/dashboardErrorContainer.component';
import { DashboardNavSection } from 'sql/parts/dashboard/containers/dashboardNavSection.component';
import { WidgetContent } from 'sql/parts/dashboard/contents/widgetContent.component';
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
import { ModelComponentWrapper } from 'sql/parts/modelComponents/modelComponentWrapper.component';
import { WebviewContent } from 'sql/parts/dashboard/contents/webviewContent.component';
import { BreadcrumbComponent } from 'sql/base/browser/ui/breadcrumb/breadcrumb.component';
import { IBreadcrumbService } from 'sql/base/browser/ui/breadcrumb/interfaces';
@@ -49,9 +54,9 @@ import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.comp
import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, WebviewContent, WidgetContent,
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent];
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper];
/* Panel */
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
@@ -81,6 +86,9 @@ let widgetComponents = [
/* Insights */
let insightComponents = Registry.as<IInsightRegistry>(Extensions.InsightContribution).getAllCtors();
/* Model-backed components */
let extensionComponents = Registry.as<IComponentRegistry>(ComponentExtensions.ComponentContribution).getAllCtors();
// Setup routes for various child components
const appRoutes: Routes = [
{ path: 'database-dashboard', component: DatabaseDashboardPage },
@@ -99,13 +107,15 @@ const appRoutes: Routes = [
...baseComponents,
...pageComponents,
...widgetComponents,
...insightComponents
...insightComponents,
...extensionComponents
],
// also for widgets
entryComponents: [
DashboardComponent,
...widgetComponents,
...insightComponents
...insightComponents,
...extensionComponents
],
imports: [
CommonModule,
@@ -119,18 +129,19 @@ const appRoutes: Routes = [
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
{ provide: IBreadcrumbService, useClass: BreadcrumbService },
DashboardServiceInterface,
{ provide: CommonServiceInterface, useClass: DashboardServiceInterface },
{ provide: UrlSerializer, useClass: CustomUrlSerializer }
]
})
export class DashboardModule {
private _bootstrap: DashboardServiceInterface;
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _resolver: ComponentFactoryResolver,
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService,
@Inject(forwardRef(() => DashboardServiceInterface)) private _bootstrap: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) bootstrap: CommonServiceInterface,
@Inject(forwardRef(() => Router)) private _router: Router
) {
this._bootstrap = bootstrap as DashboardServiceInterface;
}
ngDoBootstrap(appRef: ApplicationRef) {

View File

@@ -14,6 +14,7 @@ import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/
import * as colors from 'vs/platform/theme/common/colorRegistry';
import * as nls from 'vs/nls';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
export class DatabaseDashboardPage extends DashboardPage implements OnInit {
protected propertiesWidget: WidgetConfig = {
@@ -35,7 +36,7 @@ export class DatabaseDashboardPage extends DashboardPage implements OnInit {
constructor(
@Inject(forwardRef(() => IBreadcrumbService)) private _breadcrumbService: IBreadcrumbService,
@Inject(forwardRef(() => DashboardServiceInterface)) dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) _cd: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {

View File

@@ -10,6 +10,7 @@ import { BreadcrumbClass } from 'sql/parts/dashboard/services/breadcrumb.service
import { IBreadcrumbService } from 'sql/base/browser/ui/breadcrumb/interfaces';
import { WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
import * as colors from 'vs/platform/theme/common/colorRegistry';
@@ -36,7 +37,7 @@ export class ServerDashboardPage extends DashboardPage implements OnInit {
constructor(
@Inject(forwardRef(() => IBreadcrumbService)) private breadcrumbService: IBreadcrumbService,
@Inject(forwardRef(() => DashboardServiceInterface)) dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) dashboardService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) _cd: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) el: ElementRef
) {

View File

@@ -7,6 +7,7 @@ import { Injectable, forwardRef, Inject, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { DashboardServiceInterface } from './dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { MenuItem, IBreadcrumbService } from 'sql/base/browser/ui/breadcrumb/interfaces';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
@@ -23,9 +24,11 @@ export class BreadcrumbService implements IBreadcrumbService {
public breadcrumbItem: Subject<MenuItem[]>;
private itemBreadcrums: MenuItem[];
private _currentPage: BreadcrumbClass;
private _bootstrap: DashboardServiceInterface;
constructor( @Inject(forwardRef(() => DashboardServiceInterface)) private _bootstrap: DashboardServiceInterface) {
_bootstrap.onUpdatePage(() => {
constructor( @Inject(forwardRef(() => CommonServiceInterface)) private commonService: CommonServiceInterface) {
this._bootstrap = commonService as DashboardServiceInterface;
this._bootstrap.onUpdatePage(() => {
this.setBreadcrumbs(this._currentPage);
});
this.breadcrumbItem = new Subject<MenuItem[]>();

View File

@@ -23,9 +23,11 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { AngularEventType, IAngularEvent, IAngularEventingService } from 'sql/services/angularEventing/angularEventingService';
import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
import { TabSettingConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { IDashboardWebviewService } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
import { IDashboardViewService } from 'sql/services/dashboard/common/dashboardViewService';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { ConnectionContextkey } from 'sql/parts/connection/common/connectionContextKey';
import { SingleConnectionMetadataService, SingleConnectionManagementService, SingleAdminService, SingleQueryManagementService, CommonServiceInterface }
from 'sql/services/common/commonServiceInterface.service';
import { ProviderMetadata, DatabaseInfo, SimpleExecuteResult } from 'sqlops';
@@ -50,68 +52,6 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
const DASHBOARD_SETTINGS = 'dashboard';
/* Wrapper for a metadata service that contains the uri string to use on each request */
export class SingleConnectionMetadataService {
constructor(
private _metadataService: IMetadataService,
private _uri: string
) { }
get metadata(): Observable<ProviderMetadata> {
return Observable.fromPromise(this._metadataService.getMetadata(this._uri));
}
get databaseNames(): Observable<string[]> {
return Observable.fromPromise(this._metadataService.getDatabaseNames(this._uri));
}
}
/* Wrapper for a connection service that contains the uri string to use on each request */
export class SingleConnectionManagementService {
constructor(
private _connectionService: IConnectionManagementService,
private _uri: string,
private _contextKey: ConnectionContextkey
) { }
public changeDatabase(name: string): Thenable<boolean> {
return this._connectionService.changeDatabase(this._uri, name).then(e => {
// we need to update our context
this._contextKey.set(this.connectionInfo.connectionProfile);
return e;
});
}
public get connectionInfo(): ConnectionManagementInfo {
return this._connectionService.getConnectionInfo(this._uri);
}
}
export class SingleAdminService {
constructor(
private _adminService: IAdminService,
private _uri: string
) { }
public get databaseInfo(): Observable<DatabaseInfo> {
return Observable.fromPromise(this._adminService.getDatabaseInfo(this._uri));
}
}
export class SingleQueryManagementService {
constructor(
private _queryManagementService: IQueryManagementService,
private _uri: string
) { }
public runQueryAndReturn(queryString: string): Thenable<SimpleExecuteResult> {
return this._queryManagementService.runQueryAndReturn(this._uri, queryString);
}
}
/*
Providers a interface between a dashboard interface and the rest of carbon.
Stores the uri and unique selector of a dashboard instance and uses that
@@ -120,35 +60,12 @@ export class SingleQueryManagementService {
usage of a widget.
*/
@Injectable()
export class DashboardServiceInterface extends AngularDisposable {
private _uniqueSelector: string;
private _uri: string;
private _bootstrapParams: DashboardComponentParams;
export class DashboardServiceInterface extends CommonServiceInterface {
/* Static Services */
private _themeService = this._bootstrapService.themeService;
private _contextMenuService = this._bootstrapService.contextMenuService;
private _instantiationService = this._bootstrapService.instantiationService;
private _configService = this._bootstrapService.configurationService;
private _insightsDialogService = this._bootstrapService.insightsDialogService;
private _contextViewService = this._bootstrapService.contextViewService;
private _notificationService = this._bootstrapService.notificationService;
private _workspaceContextService = this._bootstrapService.workspaceContextService;
private _storageService = this._bootstrapService.storageService;
private _capabilitiesService = this._bootstrapService.capabilitiesService;
private _configurationEditingService = this._bootstrapService.configurationEditorService;
private _commandService = this._bootstrapService.commandService;
private _dashboardWebviewService = this._bootstrapService.dashboardWebviewService;
private _partService = this._bootstrapService.partService;
private _angularEventingService = this._bootstrapService.angularEventingService;
private _environmentService = this._bootstrapService.environmentService;
/* Special Services */
private _metadataService: SingleConnectionMetadataService;
private _connectionManagementService: SingleConnectionManagementService;
private _adminService: SingleAdminService;
private _queryManagementService: SingleQueryManagementService;
private _contextKeyService: IContextKeyService;
private _dashboardViewService = this._bootstrapService.dashboardViewService;
private _updatePage = new Emitter<void>();
public readonly onUpdatePage: Event<void> = this._updatePage.event;
@@ -168,91 +85,17 @@ export class DashboardServiceInterface extends AngularDisposable {
private _dashboardContextKey = new RawContextKey<string>('dashboardContext', undefined);
public dashboardContextKey: IContextKey<string>;
private _connectionContextKey: ConnectionContextkey;
private _numberOfPageNavigations = 0;
constructor(
@Inject(BOOTSTRAP_SERVICE_ID) private _bootstrapService: IBootstrapService,
@Inject(BOOTSTRAP_SERVICE_ID) bootstrapService: IBootstrapService,
@Inject(forwardRef(() => Router)) private _router: Router,
) {
super();
super(bootstrapService);
}
public get notificationService(): INotificationService {
return this._notificationService;
}
public get configurationEditingService(): ConfigurationEditingService {
return this._configurationEditingService;
}
public get metadataService(): SingleConnectionMetadataService {
return this._metadataService;
}
public get connectionManagementService(): SingleConnectionManagementService {
return this._connectionManagementService;
}
public get commandService(): ICommandService {
return this._commandService;
}
public get themeService(): IWorkbenchThemeService {
return this._themeService;
}
public get contextMenuService(): IContextMenuService {
return this._contextMenuService;
}
public get instantiationService(): IInstantiationService {
return this._instantiationService;
}
public get dashboardWebviewService(): IDashboardWebviewService {
return this._dashboardWebviewService;
}
public get partService(): IPartService {
return this._partService;
}
public get contextKeyService(): IContextKeyService {
return this._contextKeyService;
}
public get adminService(): SingleAdminService {
return this._adminService;
}
public get queryManagementService(): SingleQueryManagementService {
return this._queryManagementService;
}
public get environmentService(): IEnvironmentService {
return this._environmentService;
}
public get contextViewService(): IContextViewService {
return this._contextViewService;
}
public get workspaceContextService(): IWorkspaceContextService {
return this._workspaceContextService;
}
public get storageService(): IStorageService {
return this._storageService;
}
public get capabilitiesService(): ICapabilitiesService {
return this._capabilitiesService;
}
public get angularEventingService(): IAngularEventingService {
return this._angularEventingService;
public get dashboardViewService(): IDashboardViewService {
return this._dashboardViewService;
}
/**
@@ -263,7 +106,7 @@ export class DashboardServiceInterface extends AngularDisposable {
this._getbootstrapParams();
}
private _getbootstrapParams(): void {
protected _getbootstrapParams(): void {
this._bootstrapParams = this._bootstrapService.getBootstrapParams<DashboardComponentParams>(this._uniqueSelector);
this._contextKeyService = this._bootstrapParams.scopedContextService;
this._connectionContextKey = this._bootstrapParams.connectionContextKey;
@@ -275,27 +118,11 @@ export class DashboardServiceInterface extends AngularDisposable {
* Set the uri for this dashboard instance, should only be set once
* Inits all the services that depend on knowing a uri
*/
private set uri(uri: string) {
this._uri = uri;
this._metadataService = new SingleConnectionMetadataService(this._bootstrapService.metadataService, this._uri);
this._connectionManagementService = new SingleConnectionManagementService(this._bootstrapService.connectionManagementService, this._uri, this._connectionContextKey);
this._adminService = new SingleAdminService(this._bootstrapService.adminService, this._uri);
this._queryManagementService = new SingleQueryManagementService(this._bootstrapService.queryManagementService, this._uri);
protected set uri(uri: string) {
super.setUri(uri);
this._register(toDisposableSubscription(this._bootstrapService.angularEventingService.onAngularEvent(this._uri, (event) => this.handleDashboardEvent(event))));
}
/**
* Gets the underlying Uri for dashboard
* In general don't use this, use specific services instances exposed publically
*/
public getUnderlyingUri(): string {
return this._uri;
}
public getOriginalConnectionProfile(): IConnectionProfile {
return this._bootstrapParams.connection;
}
/**
* Gets the number of page navigation
*/

View File

@@ -7,7 +7,7 @@ import { Router } from '@angular/router';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { MetadataType } from 'sql/parts/connection/common/connectionManagement';
import { SingleConnectionManagementService } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
import {
NewQueryAction, ScriptSelectAction, EditDataAction, ScriptCreateAction, ScriptExecuteAction, ScriptAlterAction,
BackupAction, ManageActionContext, BaseActionContext, ManageAction, RestoreAction

View File

@@ -12,6 +12,7 @@ import { Router } from '@angular/router';
import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
import { ExplorerFilter, ExplorerRenderer, ExplorerDataSource, ExplorerController, ObjectMetadataWrapper, ExplorerModel } from './explorerTree';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
@@ -49,7 +50,7 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
@ViewChild('table') private _tableContainer: ElementRef;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) private _bootstrap: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface,
@Inject(forwardRef(() => Router)) private _router: Router,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig,
@@ -62,8 +63,11 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
ngOnInit() {
this._inited = true;
let placeholderLabel = this._config.context === 'database' ? nls.localize('seachObjects', 'Search by name of type (a:, t:, v:, f:, or sp:)') : nls.localize('searchDatabases', 'Search databases');
let inputOptions: IInputOptions = {
placeholder: this._config.context === 'database' ? nls.localize('seachObjects', 'Search by name of type (a:, t:, v:, f:, or sp:)') : nls.localize('searchDatabases', 'Search databases')
placeholder: placeholderLabel,
ariaLabel: placeholderLabel
};
this._input = new InputBox(this._inputContainer.nativeElement, this._bootstrap.contextViewService, inputOptions);
this._register(this._input.onDidChange(e => {

View File

@@ -10,6 +10,7 @@ import { Observable } from 'rxjs/Observable';
import { DashboardWidget, IDashboardWidget, WIDGET_CONFIG, WidgetConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { ComponentHostDirective } from 'sql/parts/dashboard/common/componentHost.directive';
import { InsightAction, InsightActionContext } from 'sql/workbench/common/actions';
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
@@ -26,6 +27,7 @@ import * as pfs from 'vs/base/node/pfs';
import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IntervalTimer } from 'vs/base/common/async';
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
@@ -51,13 +53,14 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
private _typeKey: string;
private _init: boolean = false;
private _intervalTimer: IntervalTimer;
public error: string;
public lastUpdated: string;
constructor(
@Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver,
@Inject(forwardRef(() => DashboardServiceInterface)) private dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) private dashboardService: CommonServiceInterface,
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig,
@Inject(forwardRef(() => ViewContainerRef)) private viewContainerRef: ViewContainerRef,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
@@ -75,6 +78,7 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
result => {
if (this._init) {
this._updateChild(result);
this.setupInterval();
} else {
this.queryObv = Observable.fromPromise(Promise.resolve<SimpleExecuteResult>(result));
}
@@ -99,6 +103,7 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
this._register(toDisposableSubscription(this.queryObv.subscribe(
result => {
this._updateChild(result);
this.setupInterval();
},
error => {
this.showError(error);
@@ -107,6 +112,14 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
}
}
private setupInterval(): void {
if (this.insightConfig.autoRefreshInterval) {
this._intervalTimer = new IntervalTimer();
this._register(this._intervalTimer);
this._intervalTimer.cancelAndSet(() => this.refresh(), this.insightConfig.autoRefreshInterval * 60 * 1000);
}
}
private showError(error: string): void {
this.error = error;
this._cd.detectChanges();
@@ -151,6 +164,7 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
this.lastUpdated = nls.localize('insights.lastUpdated', "Last Updated: {0} {1}", date.toLocaleTimeString(), date.toLocaleDateString());
if (this._init) {
this._updateChild(storedResult.results);
this.setupInterval();
this._cd.detectChanges();
} else {
this.queryObv = Observable.fromPromise(Promise.resolve<SimpleExecuteResult>(JSON.parse(storage)));
@@ -230,6 +244,10 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
throw new Error('No query was specified for this insight');
}
if (this.insightConfig.autoRefreshInterval && !types.isNumber(this.insightConfig.autoRefreshInterval)) {
throw new Error('Auto Refresh Interval must be a number if specified');
}
if (!types.isStringArray(this.insightConfig.query)
&& !types.isString(this.insightConfig.query)
&& !types.isString(this.insightConfig.queryFile)) {

View File

@@ -6,7 +6,7 @@ import { join } from 'path';
import { registerDashboardWidget, registerNonCustomDashboardWidget } from 'sql/platform/dashboard/common/widgetRegistry';
import { Extensions as InsightExtensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry';
import { IInsightsConfig } from './interfaces';
import { IInsightsConfig, IInsightTypeContrib } from './interfaces';
import { insightsContribution, insightsSchema } from 'sql/parts/dashboard/widgets/insights/insightsWidgetSchemas';
import { IExtensionPointUser, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
@@ -14,10 +14,6 @@ import { Registry } from 'vs/platform/registry/common/platform';
const insightRegistry = Registry.as<IInsightRegistry>(InsightExtensions.InsightContribution);
interface IInsightTypeContrib {
id: string;
contrib: IInsightsConfig;
}
registerDashboardWidget('insights-widget', '', insightsSchema);

View File

@@ -32,6 +32,10 @@ export const insightsSchema: IJSONSchema = {
type: 'string',
description: nls.localize('insightQueryFileDescription', '[Optional] path to a file that contains a query. Use if "query" is not set')
},
autoRefreshInterval: {
type: 'number',
description: nls.localize('insightAutoRefreshIntervalDescription', '[Optional] Auto refresh interval in minutes, if not set, there will be no auto refresh')
},
details: {
type: 'object',
properties: {
@@ -93,15 +97,15 @@ export const insightsSchema: IJSONSchema = {
},
database: {
type: 'string',
description: nls.localize('actionDatabaseDescription', 'Target database for the action; can use the format "${columnName} to use a data driven column name.')
description: nls.localize('actionDatabaseDescription', 'Target database for the action; can use the format "${columnName}" to use a data driven column name.')
},
server: {
type: 'string',
description: nls.localize('actionServerDescription', 'Target server for the action; can use the format "${columnName} to use a data driven column name.')
description: nls.localize('actionServerDescription', 'Target server for the action; can use the format "${columnName}" to use a data driven column name.')
},
user: {
type: 'string',
description: nls.localize('actionUserDescription', 'Target user for the action; can use the format "${columnName} to use a data driven column name.')
description: nls.localize('actionUserDescription', 'Target user for the action; can use the format "${columnName}" to use a data driven column name.')
}
}
}

View File

@@ -56,4 +56,9 @@ export interface IInsightsConfig {
query?: string | Array<string>;
queryFile?: string;
details?: IInsightsConfigDetails;
autoRefreshInterval?: number;
}
export interface IInsightTypeContrib {
id: string;
contrib: IInsightsConfig;
}

View File

@@ -79,6 +79,7 @@ export interface IChartConfig {
legendPosition?: LegendPosition;
dataDirection?: DataDirection;
columnsAsLabels?: boolean;
showTopNData?: number;
}
export const defaultChartConfig: IChartConfig = {
@@ -179,7 +180,7 @@ export abstract class ChartInsight extends Disposable implements IInsightsView {
// unmemoize chart data as the data needs to be recalced
unmemoize(this, 'chartData');
unmemoize(this, 'labels');
this._data = data;
this._data = this.filterToTopNData(data);
if (isValidData(data)) {
this._isDataAvailable = true;
}
@@ -187,6 +188,34 @@ export abstract class ChartInsight extends Disposable implements IInsightsView {
this._changeRef.detectChanges();
}
private filterToTopNData(data: IInsightData): IInsightData {
if (this._config.dataDirection === 'horizontal') {
return {
columns: this.getTopNData(data.columns),
rows: data.rows.map((row) => {
return this.getTopNData(row);
})
};
} else {
return {
columns: data.columns,
rows: data.rows.slice(0, this._config.showTopNData)
};
}
}
private getTopNData(data: any[]): any[] {
if (this._config.showTopNData) {
if (this._config.dataDirection === 'horizontal' && this._config.labelFirstColumn) {
return data.slice(0, this._config.showTopNData + 1);
} else {
return data.slice(0, this._config.showTopNData);
}
} else {
return data;
}
}
protected clearMemoize(): void {
// unmemoize getters since their result can be changed by a new config
unmemoize(this, 'getChartData');
@@ -250,7 +279,11 @@ export abstract class ChartInsight extends Disposable implements IInsightsView {
@memoize
public getLabels(): Array<string> {
if (this._config.dataDirection === 'horizontal') {
return this._data.columns;
if (this._config.labelFirstColumn) {
return this._data.columns.slice(1);
} else {
return this._data.columns;
}
} else {
return this._data.rows.map(row => row[0]);
}

View File

@@ -35,6 +35,10 @@ export const chartInsightSchema: IJSONSchema = {
default: 'vertical',
enum: ['vertical', 'horizontal'],
enumDescriptions: ['When vertical, the first column is used to define the x-axis labels, with other columns expected to be numerical.', 'When horizontal, the column names are used as the x-axis labels.']
},
showTopNData: {
type: 'number',
description: nls.localize('showTopNData', 'If showTopNData is set, showing only top N data in the chart.')
}
}
};

View File

@@ -12,7 +12,7 @@ import { chartInsightSchema } from 'sql/parts/dashboard/widgets/insights/views/c
import BarChart from './barChart.component';
export const properties: IJSONSchema = {
const properties: IJSONSchema = {
properties: {
yAxisMin: {
type: 'number',
@@ -41,6 +41,6 @@ export const properties: IJSONSchema = {
}
};
const barSchema = mixin(clone(chartInsightSchema), properties) as IJSONSchema;
export const barChartSchema = mixin(clone(chartInsightSchema), properties) as IJSONSchema;
registerInsight('bar', '', barSchema, BarChart);
registerInsight('bar', '', barChartSchema, BarChart);

View File

@@ -7,7 +7,7 @@ import { mixin } from 'vs/base/common/objects';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { registerInsight } from 'sql/platform/dashboard/common/insightRegistry';
import { properties as BarChartSchema } from 'sql/parts/dashboard/widgets/insights/views/charts/types/barChart.contribution';
import { barChartSchema } from 'sql/parts/dashboard/widgets/insights/views/charts/types/barChart.contribution';
import HorizontalBarChart from './horizontalBarChart.component';
@@ -15,6 +15,6 @@ const properties: IJSONSchema = {
};
const horizontalBarSchema = mixin(clone(BarChartSchema), properties) as IJSONSchema;
const horizontalBarSchema = mixin(clone(barChartSchema), properties) as IJSONSchema;
registerInsight('horizontalBar', '', horizontalBarSchema, HorizontalBarChart);

View File

@@ -8,7 +8,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as nls from 'vs/nls';
import { registerInsight } from 'sql/platform/dashboard/common/insightRegistry';
import { properties as BarChartSchema } from 'sql/parts/dashboard/widgets/insights/views/charts/types/barChart.contribution';
import { barChartSchema } from 'sql/parts/dashboard/widgets/insights/views/charts/types/barChart.contribution';
import LineChart from './lineChart.component';
@@ -24,6 +24,6 @@ const properties: IJSONSchema = {
}
};
export const lineSchema = mixin(clone(BarChartSchema), properties) as IJSONSchema;
export const lineSchema = mixin(clone(barChartSchema), properties) as IJSONSchema;
registerInsight('line', '', lineSchema, LineChart);

View File

@@ -7,13 +7,13 @@ import { clone } from 'sql/base/common/objects';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { registerInsight } from 'sql/platform/dashboard/common/insightRegistry';
import { properties as BarChartSchema } from 'sql/parts/dashboard/widgets/insights/views/charts/types/barChart.contribution';
import { barChartSchema } from 'sql/parts/dashboard/widgets/insights/views/charts/types/barChart.contribution';
import ScatterChart from './scatterChart.component';
const properties: IJSONSchema = {
};
const scatterSchema = mixin(clone(BarChartSchema), properties) as IJSONSchema;
const scatterSchema = mixin(clone(barChartSchema), properties) as IJSONSchema;
registerInsight('scatter', '', scatterSchema, ScatterChart);

View File

@@ -7,13 +7,13 @@ import { clone } from 'sql/base/common/objects';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { registerInsight } from 'sql/platform/dashboard/common/insightRegistry';
import { properties as BarChartSchema } from 'sql/parts/dashboard/widgets/insights/views/charts/types/barChart.contribution';
import { barChartSchema } from 'sql/parts/dashboard/widgets/insights/views/charts/types/barChart.contribution';
import TimeSeriesChart from './timeSeriesChart.component';
const properties: IJSONSchema = {
};
const timeSeriesSchema = mixin(clone(BarChartSchema), properties) as IJSONSchema;
const timeSeriesSchema = mixin(clone(barChartSchema), properties) as IJSONSchema;
registerInsight('timeSeries', '', timeSeriesSchema, TimeSeriesChart);

View File

@@ -7,6 +7,7 @@ import { Component, Inject, forwardRef, ChangeDetectorRef, OnInit, ElementRef, V
import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
import { toDisposableSubscription } from 'sql/parts/common/rxjsUtils';
import { error } from 'sql/base/common/log';
@@ -68,7 +69,7 @@ export class PropertiesWidgetComponent extends DashboardWidget implements IDashb
@ViewChild('parent', { read: ElementRef }) private _parent: ElementRef;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) private _bootstrap: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig,

View File

@@ -12,6 +12,7 @@ import { DomSanitizer } from '@angular/platform-browser';
/* SQL imports */
import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { TaskRegistry } from 'sql/platform/tasks/common/tasks';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { BaseActionContext } from 'sql/workbench/common/actions';
@@ -51,13 +52,14 @@ export class TasksWidget extends DashboardWidget implements IDashboardWidget, On
private _profile: IConnectionProfile;
private _scrollableElement: ScrollableElement;
private $container: Builder;
static readonly ICON_PATH_TO_CSS_RULES: Map<string /* path*/, string /* CSS rule */> = new Map<string, string>();
private _inited = false;
@ViewChild('container', { read: ElementRef }) private _container: ElementRef;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) private _bootstrap: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrap: CommonServiceInterface,
@Inject(forwardRef(() => DomSanitizer)) private _sanitizer: DomSanitizer,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeref: ChangeDetectorRef,
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig
@@ -130,9 +132,9 @@ export class TasksWidget extends DashboardWidget implements IDashboardWidget, On
let tile = $('div.task-tile').style('height', this._size + 'px').style('width', this._size + 'px');
let innerTile = $('div');
// @SQLTODO - iconPath shouldn't be used as a CSS class
if (action.iconPath && action.iconPath.dark) {
let icon = $('span.icon').addClass(action.iconPath.dark);
let iconClassName = TaskRegistry.getOrCreateTaskIconClassName(action);
if (iconClassName) {
let icon = $('span.icon').addClass(iconClassName);
innerTile.append(icon);
}
innerTile.append(label);

View File

@@ -13,7 +13,8 @@ import { memoize } from 'vs/base/common/decorators';
import { DashboardWidget, IDashboardWidget, WidgetConfig, WIDGET_CONFIG } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { IDashboardWebview } from 'sql/services/dashboardWebview/common/dashboardWebviewService';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { IDashboardWebview } from 'sql/services/dashboard/common/dashboardViewService';
import * as sqlops from 'sqlops';
@@ -35,19 +36,21 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget,
private _onMessage = new Emitter<string>();
public readonly onMessage: Event<string> = this._onMessage.event;
private _onMessageDisposable: IDisposable;
private _dashboardService: DashboardServiceInterface;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) private _dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) private commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(WIDGET_CONFIG) protected _config: WidgetConfig,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef
) {
super();
this._id = (_config.widget[selector] as IWebviewWidgetConfig).id;
this._dashboardService = commonService as DashboardServiceInterface;
}
ngOnInit() {
this._dashboardService.dashboardWebviewService.registerWebview(this);
this._dashboardService.dashboardViewService.registerWebview(this);
this._createWebview();
}

View File

@@ -8,24 +8,24 @@
<div class="angular-modal-body" style="display: flex; flex-direction: column;">
<div class="angular-modal-body-content">
<div class="dialog-label">
{{backupNameLabel}}
{{localizedStrings.BACKUP_NAME}}
</div>
<div class="input-divider" #backupsetName>
</div>
<div class="dialog-label">
{{recoveryModelLabel}}
{{localizedStrings.RECOVERY_MODEL}}
</div>
<div class="input-divider" #recoveryModelContainer>
</div>
<div class="dialog-label">
{{backupTypeLabel}}
{{localizedStrings.BACKUP_TYPE}}
</div>
<div class="input-divider" #backupTypeContainer>
</div>
<div class="input-divider check" #copyOnlyContainer>
</div>
<div class="dialog-label">
{{backupDeviceLabel}}
{{localizedStrings.BACKUP_DEVICE}}
</div>
<div class="backup-path-list">
<div #pathContainer>
@@ -47,11 +47,11 @@
<div class="advanced-main-body" #advancedOptionBodyContainer>
<!-- Compression -->
<div class="dialog-label advanced-header">
{{compressionLabel}}
{{localizedStrings.COMPRESSION}}
</div>
<div class="indent">
<div class="dialog-label">
{{setBackupCompressionLabel}}
{{localizedStrings.SET_BACKUP_COMPRESSION}}
</div>
<div class="dialog-label" #compressionContainer>
</div>
@@ -59,7 +59,7 @@
<!-- Encryption -->
<div class="dialog-label advanced-header">
{{encryptionLabel}}
{{localizedStrings.ENCRYPTION}}
</div>
<div class="indent">
<div class="option check" #encryptCheckContainer>
@@ -68,17 +68,17 @@
<div class="icon warning">
</div>
<div class="warning-message">
{{noEncryptorWarning}}
{{localizedStrings.NO_ENCRYPTOR_WARNING}}
</div>
</div>
<div #encryptContainer>
<div class="dialog-label">
{{algorithmLabel}}
{{localizedStrings.ALGORITHM}}
</div>
<div class="dialog-label" #algorithmContainer>
</div>
<div class="dialog-label">
{{certificateOrAsymmetricKeyLabel}}
{{localizedStrings.CERTIFICATE_OR_ASYMMETRIC_KEY}}
</div>
<div class="dialog-label" #encryptorContainer>
</div>
@@ -86,33 +86,33 @@
</div>
<!-- Overwrite media -->
<div class="dialog-label advanced-header">
{{mediaLabel}}
<div id="media" class="dialog-label advanced-header">
{{localizedStrings.MEDIA}}
</div>
<div class="radio-indent">
<div role="radiogroup" aria-labelledby="media" class="radio-indent">
<div class="option">
<input type="radio" name=media-option value="no_format" [checked]="!isFormatChecked" (change)="onChangeMediaFormat()" [disabled]="isEncryptChecked">{{mediaOptionLabel}}
<input role="radio" type="radio" name="media-option" value="no_format" [checked]="!isFormatChecked" (change)="onChangeMediaFormat()" [disabled]="isEncryptChecked" aria-labelledby="mediaOption"><span id="mediaOption">{{localizedStrings.MEDIA_OPTION}}</span>
</div>
<div style="margin-left:15px">
<div role="radiogroup" aria-labelledby="mediaOption" style="margin-left:15px">
<div class="option">
<input type="radio" name=existing-media value="append" [(ngModel)]="selectedInitOption" [disabled]="isFormatChecked">{{existingMediaAppendLabel}}
<input role="radio" type="radio" name="existing-media" value="append" [(ngModel)]="selectedInitOption" [disabled]="isFormatChecked" aria-labelledby="existingMediaAppend"><span id="existingMediaAppend">{{localizedStrings.EXISTING_MEDIA_APPEND}}</span>
</div>
<div class="option">
<input type="radio" name=existing-media value="overwrite" [(ngModel)]="selectedInitOption" [disabled]="isFormatChecked">{{existingMediaOverwriteLabel}}
<input role="radio" type="radio" name="existing-media" value="overwrite" [(ngModel)]="selectedInitOption" [disabled]="isFormatChecked" aria-labelledby="existingMediaOverwrite"><span id="existingMediaOverwrite">{{localizedStrings.EXISTING_MEDIA_OVERWRITE}}</span>
</div>
</div>
<div class="option">
<input type="radio" name=media-option value="format" [checked]="isFormatChecked" (change)="onChangeMediaFormat()">{{mediaOptionFormatLabel}}
<input role="radio" type="radio" name="media-option" value="format" [checked]="isFormatChecked" (change)="onChangeMediaFormat()" aria-labelledby="mediaOptionFormat"><span id="mediaOptionFormat">{{localizedStrings.MEDIA_OPTION_FORMAT}}</span>
</div>
<div style="margin-left: 22px">
<div class="dialog-label">
{{newMediaSetNameLabel}}
{{localizedStrings.NEW_MEDIA_SET_NAME}}
</div>
<div class="dialog-label" #mediaName>
</div>
<div class="dialog-label">
{{newMediaSetDescriptionLabel}}
{{localizedStrings.NEW_MEDIA_SET_DESCRIPTION}}
</div>
<div class="dialog-label" #mediaDescription>
</div>
@@ -120,21 +120,21 @@
</div>
<!-- Transaction log -->
<div class="dialog-label advanced-header">
{{transactionLogLabel}}
<div id="transactionLog" class="dialog-label advanced-header">
{{localizedStrings.TRANSACTION_LOG}}
</div>
<div class="radio-indent">
<div role="radiogroup" aria-labelledby="transactionLog" class="radio-indent">
<div class="option">
<input type="radio" name=t-log value="truncate" [checked]="isTruncateChecked" (change)="onChangeTlog()" [disabled]="disableTlog">Truncate the transaction log
<input role="radio" type="radio" name="t-log" value="truncate" [checked]="isTruncateChecked" (change)="onChangeTlog()" [disabled]="disableTlog" aria-labelledby="truncateTransaction"><span id="truncateTransaction">{{localizedStrings.TRUNCATE_TRANSACTION_LOG}}</span>
</div>
<div class="option">
<input type="radio" name=t-log value="taillog" [checked]="isTaillogChecked" (change)="onChangeTlog()" [disabled]="disableTlog">Backup the tail of the log
<input role="radio" type="radio" name="t-log" value="taillog" [checked]="isTaillogChecked" (change)="onChangeTlog()" [disabled]="disableTlog" aria-labelledby="backupTail"><span id="backupTail">{{localizedStrings.BACKUP_TAIL}}</span>
</div>
</div>
<!-- Reliability -->
<div class="dialog-label advanced-header">
{{reliabilityLabel}}
{{localizedStrings.RELIABILITY}}
</div>
<div class="indent">
<div class="option check" #checksumContainer>
@@ -147,11 +147,11 @@
<!-- Backup expiration -->
<div class="dialog-label advanced-header">
{{expirationLabel}}
{{localizedStrings.EXPIRATION}}
</div>
<div class="indent">
<div class="dialog-label">
{{setBackupRetainDaysLabel}}
{{localizedStrings.SET_BACKUP_RETAIN_DAYS}}
</div>
<div class="dialog-label">
<div #backupDaysContainer></div>

View File

@@ -76,6 +76,38 @@ interface MssqlBackupInfo {
encryptorName: string;
}
const LocalizedStrings = {
BACKUP_NAME: localize('backup.backupName', 'Backup name'),
RECOVERY_MODEL: localize('backup.recoveryModel', 'Recovery model'),
BACKUP_TYPE: localize('backup.backupType', 'Backup type'),
BACKUP_DEVICE: localize('backup.backupDevice', 'Backup files'),
ALGORITHM: localize('backup.algorithm', 'Algorithm'),
CERTIFICATE_OR_ASYMMETRIC_KEY: localize('backup.certificateOrAsymmetricKey', 'Certificate or Asymmetric key'),
MEDIA: localize('backup.media', 'Media'),
MEDIA_OPTION: localize('backup.mediaOption', 'Backup to the existing media set'),
MEDIA_OPTION_FORMAT: localize('backup.mediaOptionFormat', 'Backup to a new media set'),
EXISTING_MEDIA_APPEND: localize('backup.existingMediaAppend', 'Append to the existing backup set'),
EXISTING_MEDIA_OVERWRITE: localize('backup.existingMediaOverwrite', 'Overwrite all existing backup sets'),
NEW_MEDIA_SET_NAME: localize('backup.newMediaSetName', 'New media set name'),
NEW_MEDIA_SET_DESCRIPTION: localize('backup.newMediaSetDescription', 'New media set description'),
CHECKSUM_CONTAINER: localize('backup.checksumContainer', 'Perform checksum before writing to media'),
VERIFY_CONTAINER: localize('backup.verifyContainer', 'Verify backup when finished'),
CONTINUE_ON_ERROR_CONTAINER: localize('backup.continueOnErrorContainer', 'Continue on error'),
EXPIRATION: localize('backup.expiration', 'Expiration'),
SET_BACKUP_RETAIN_DAYS: localize('backup.setBackupRetainDays', 'Set backup retain days'),
COPY_ONLY: localize('backup.copyOnly', 'Copy-only backup'),
ADVANCED_CONFIGURATION: localize('backup.advancedConfiguration', 'Advanced Configuration'),
COMPRESSION: localize('backup.compression', 'Compression'),
SET_BACKUP_COMPRESSION: localize('backup.setBackupCompression', 'Set backup compression'),
ENCRYPTION: localize('backup.encryption', 'Encryption'),
TRANSACTION_LOG: localize('backup.transactionLog', 'Transaction log'),
TRUNCATE_TRANSACTION_LOG: localize('backup.truncateTransactionLog', 'Truncate the transaction log'),
BACKUP_TAIL: localize('backup.backupTail', 'Backup the tail of the log'),
RELIABILITY: localize('backup.reliability', 'Reliability'),
MEDIA_NAME_REQUIRED_ERROR: localize('backup.mediaNameRequired', 'Media name is required'),
NO_ENCRYPTOR_WARNING: localize('backup.noEncryptorWarning', "No certificate or asymmetric key is available")
};
@Component({
selector: BACKUP_SELECTOR,
templateUrl: decodeURI(require.toUrl('sql/parts/disasterRecovery/backup/backup.component.html'))
@@ -109,36 +141,7 @@ export class BackupComponent {
@ViewChild('advancedOptionContainer', { read: ElementRef }) advancedOptionElement;
@ViewChild('advancedOptionBodyContainer', { read: ElementRef }) advancedOptionBodyElement;
// tslint:disable:no-unused-variable
private readonly backupNameLabel: string = localize('backup.backupName', 'Backup name');
private readonly recoveryModelLabel: string = localize('backup.recoveryModel', 'Recovery model');
private readonly backupTypeLabel: string = localize('backup.backupType', 'Backup type');
private readonly backupDeviceLabel: string = localize('backup.backupDevice', 'Backup files');
private readonly algorithmLabel: string = localize('backup.algorithm', 'Algorithm');
private readonly certificateOrAsymmetricKeyLabel: string = localize('backup.certificateOrAsymmetricKey', 'Certificate or Asymmetric key');
private readonly mediaLabel: string = localize('backup.media', 'Media');
private readonly mediaOptionLabel: string = localize('backup.mediaOption', 'Backup to the existing media set');
private readonly mediaOptionFormatLabel: string = localize('backup.mediaOptionFormat', 'Backup to a new media set');
private readonly existingMediaAppendLabel: string = localize('backup.existingMediaAppend', 'Append to the existing backup set');
private readonly existingMediaOverwriteLabel: string = localize('backup.existingMediaOverwrite', 'Overwrite all existing backup sets');
private readonly newMediaSetNameLabel: string = localize('backup.newMediaSetName', 'New media set name');
private readonly newMediaSetDescriptionLabel: string = localize('backup.newMediaSetDescription', 'New media set description');
private readonly checksumContainerLabel: string = localize('backup.checksumContainer', 'Perform checksum before writing to media');
private readonly verifyContainerLabel: string = localize('backup.verifyContainer', 'Verify backup when finished');
private readonly continueOnErrorContainerLabel: string = localize('backup.continueOnErrorContainer', 'Continue on error');
private readonly expirationLabel: string = localize('backup.expiration', 'Expiration');
private readonly setBackupRetainDaysLabel: string = localize('backup.setBackupRetainDays', 'Set backup retain days');
private readonly copyOnlyLabel: string = localize('backup.copyOnly', 'Copy-only backup');
private readonly advancedConfigurationLabel: string = localize('backup.advancedConfiguration', 'Advanced Configuration');
private readonly compressionLabel: string = localize('backup.compression', 'Compression');
private readonly setBackupCompressionLabel: string = localize('backup.setBackupCompression', 'Set backup compression');
private readonly encryptionLabel: string = localize('backup.encryption', 'Encryption');
private readonly transactionLogLabel: string = localize('backup.transactionLog', 'Transaction log');
private readonly reliabilityLabel: string = localize('backup.reliability', 'Reliability');
private readonly mediaNameRequiredError: string = localize('backup.mediaNameRequired', 'Media name is required');
private readonly noEncryptorWarning: string = localize('backup.noEncryptorWarning', "No certificate or asymmetric key is available");
// tslint:enable:no-unused-variable
private localizedStrings = LocalizedStrings;
private _backupService: IBackupService;
private _backupUiService: IBackupUiService;
@@ -207,48 +210,58 @@ export class BackupComponent {
let self = this;
this.addFooterButtons();
this.recoveryBox = new InputBox(this.recoveryModelElement.nativeElement, this._bootstrapService.contextViewService, { placeholder: this.recoveryModel });
this.recoveryBox = new InputBox(this.recoveryModelElement.nativeElement, this._bootstrapService.contextViewService, {
placeholder: this.recoveryModel,
ariaLabel: LocalizedStrings.RECOVERY_MODEL
});
// Set backup type
this.backupTypeSelectBox = new SelectBox([], '', this._bootstrapService.contextViewService);
this.backupTypeSelectBox.render(this.backupTypeElement.nativeElement);
// Set copy-only check box
this.copyOnlyCheckBox = new Checkbox(this.copyOnlyElement.nativeElement, {
label: this.copyOnlyLabel,
label: LocalizedStrings.COPY_ONLY,
checked: false,
onChange: (viaKeyboard) => { }
onChange: (viaKeyboard) => { },
ariaLabel: LocalizedStrings.COPY_ONLY
});
// Encryption checkbox
this.encryptCheckBox = new Checkbox(this.encryptElement.nativeElement, {
label: this.encryptionLabel,
label: LocalizedStrings.ENCRYPTION,
checked: false,
onChange: (viaKeyboard) => self.onChangeEncrypt()
onChange: (viaKeyboard) => self.onChangeEncrypt(),
ariaLabel: LocalizedStrings.ENCRYPTION
});
// Verify backup checkbox
this.verifyCheckBox = new Checkbox(this.verifyElement.nativeElement, {
label: this.verifyContainerLabel,
label: LocalizedStrings.VERIFY_CONTAINER,
checked: false,
onChange: (viaKeyboard) => { }
onChange: (viaKeyboard) => { },
ariaLabel: LocalizedStrings.VERIFY_CONTAINER
});
// Perform checksum checkbox
this.checksumCheckBox = new Checkbox(this.checksumElement.nativeElement, {
label: this.checksumContainerLabel,
label: LocalizedStrings.CHECKSUM_CONTAINER,
checked: false,
onChange: (viaKeyboard) => { }
onChange: (viaKeyboard) => { },
ariaLabel: LocalizedStrings.CHECKSUM_CONTAINER
});
// Continue on error checkbox
this.continueOnErrorCheckBox = new Checkbox(this.continueOnErrorElement.nativeElement, {
label: this.continueOnErrorContainerLabel,
label: LocalizedStrings.CONTINUE_ON_ERROR_CONTAINER,
checked: false,
onChange: (viaKeyboard) => { }
onChange: (viaKeyboard) => { },
ariaLabel: LocalizedStrings.CONTINUE_ON_ERROR_CONTAINER
});
// Set backup name
this.backupNameBox = new InputBox(this.backupNameElement.nativeElement, this._bootstrapService.contextViewService);
this.backupNameBox = new InputBox(this.backupNameElement.nativeElement, this._bootstrapService.contextViewService, {
ariaLabel: LocalizedStrings.BACKUP_NAME
});
// Set backup path list
this.pathListBox = new ListBox([], '', this._bootstrapService.contextViewService, this._bootstrapService.clipboardService);
@@ -278,11 +291,15 @@ export class BackupComponent {
this._bootstrapService.contextViewService,
{
validationOptions: {
validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: this.mediaNameRequiredError }) : null
}
});
validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: LocalizedStrings.MEDIA_NAME_REQUIRED_ERROR }) : null
},
ariaLabel: LocalizedStrings.NEW_MEDIA_SET_NAME
}
);
this.mediaDescriptionBox = new InputBox(this.mediaDescriptionElement.nativeElement, this._bootstrapService.contextViewService);
this.mediaDescriptionBox = new InputBox(this.mediaDescriptionElement.nativeElement, this._bootstrapService.contextViewService, {
ariaLabel: LocalizedStrings.NEW_MEDIA_SET_DESCRIPTION
});
// Set backup retain days
let invalidInputMessage = localize('backupComponent.invalidInput', 'Invalid input. Value must be greater than or equal 0.');
@@ -300,7 +317,8 @@ export class BackupComponent {
return null;
}
}
}
},
ariaLabel: LocalizedStrings.SET_BACKUP_RETAIN_DAYS
});
// Disable elements
@@ -316,7 +334,7 @@ export class BackupComponent {
// Set category view for advanced options. This should be defined in ngAfterViewInit so that it correctly calculates the text height after data binding.
var splitview = new SplitView(this.advancedOptionElement.nativeElement);
var advancedBodySize = DOM.getTotalHeight(this.advancedOptionBodyElement.nativeElement);
var categoryView = new CategoryView(this.advancedConfigurationLabel, this.advancedOptionBodyElement.nativeElement, true, advancedBodySize, this._advancedHeaderSize);
var categoryView = new CategoryView(LocalizedStrings.ADVANCED_CONFIGURATION, this.advancedOptionBodyElement.nativeElement, true, advancedBodySize, this._advancedHeaderSize);
splitview.addView(categoryView);
splitview.layout(advancedBodySize + this._advancedHeaderSize);
@@ -584,7 +602,7 @@ export class BackupComponent {
if (strings.isFalsyOrWhitespace(this.mediaNameBox.value)) {
this.backupEnabled = false;
this.backupButton.enabled = false;
this.mediaNameBox.showMessage({ type: MessageType.ERROR, content: this.mediaNameRequiredError });
this.mediaNameBox.showMessage({ type: MessageType.ERROR, content: LocalizedStrings.MEDIA_NAME_REQUIRED_ERROR });
}
} else {
this.enableBackupButton();

View File

@@ -145,23 +145,26 @@ export class BackupUiService implements IBackupUiService {
let backupOptions = this.getOptions(this._currentProvider);
return new TPromise<void>(() => {
let uri = this._connectionManagementService.getConnectionId(connection)
+ ProviderConnectionInfo.idSeparator
+ ConnectionUtils.ConnectionUriBackupIdAttributeName
+ ProviderConnectionInfo.nameValueSeparator
+ BackupUiService._connectionUniqueId;
this._connectionUri = uri;
BackupUiService._connectionUniqueId++;
// Create connection if needed
if (!this._connectionManagementService.isConnected(uri)) {
this._connectionManagementService.connect(connection, uri).then(() => {
this._onShowBackupEvent.fire({ connection: connection, ownerUri: uri });
});
}
if (backupOptions) {
(backupDialog as OptionsDialog).open(backupOptions, self._optionValues);
} else {
let uri = this._connectionManagementService.getConnectionId(connection)
+ ProviderConnectionInfo.idSeparator
+ ConnectionUtils.ConnectionUriBackupIdAttributeName
+ ProviderConnectionInfo.nameValueSeparator
+ BackupUiService._connectionUniqueId;
BackupUiService._connectionUniqueId++;
// Create connection if needed
if (!this._connectionManagementService.isConnected(uri)) {
this._connectionManagementService.connect(connection, uri).then(() => {
this._onShowBackupEvent.fire({ connection: connection, ownerUri: uri });
});
}
(backupDialog as BackupDialog).open(connection);
}
});

View File

@@ -40,6 +40,7 @@ import * as DOM from 'vs/base/browser/dom';
import * as sqlops from 'sqlops';
import * as strings from 'vs/base/common/strings';
import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import { mixin } from 'vs/base/common/objects';
interface FileListElement {
logicalFileName: string;
@@ -48,6 +49,11 @@ interface FileListElement {
restoreAs: string;
}
const LocalizedStrings = {
BACKFILEPATH: localize('backupFilePath', "Backup file path"),
TARGETDATABASE: localize('targetDatabase', 'Target database')
};
export class RestoreDialog extends Modal {
public viewModel: RestoreViewModel;
@@ -176,12 +182,13 @@ export class RestoreDialog extends Modal {
validationOptions: {
validation: (value: string) => !value ? ({ type: MessageType.ERROR, content: errorMessage }) : null
},
placeholder: localize('multipleBackupFilePath', 'Please enter one or more file paths separated by commas')
placeholder: localize('multipleBackupFilePath', 'Please enter one or more file paths separated by commas'),
ariaLabel: LocalizedStrings.BACKFILEPATH
};
filePathContainer.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(localize('backupFilePath', "Backup file path"));
labelContainer.safeInnerHtml(LocalizedStrings.BACKFILEPATH);
});
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
@@ -218,7 +225,7 @@ export class RestoreDialog extends Modal {
destinationContainer.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(localize('targetDatabase', 'Target database'));
labelContainer.innerHtml(LocalizedStrings.TARGETDATABASE);
});
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
@@ -226,7 +233,8 @@ export class RestoreDialog extends Modal {
inputCellContainer.style('width', '100%');
this._databaseDropdown = new Dropdown(inputCellContainer.getHTMLElement(), this._contextViewService, this._themeService,
{
strictSelection: false
strictSelection: false,
ariaLabel: LocalizedStrings.TARGETDATABASE
}
);
this._databaseDropdown.onValueChange(s => {
@@ -514,7 +522,8 @@ export class RestoreDialog extends Modal {
checkbox = new Checkbox(inputCellContainer.getHTMLElement(), {
label: label,
checked: isChecked,
onChange: onCheck
onChange: onCheck,
ariaLabel: label
});
});
return checkbox;
@@ -537,13 +546,16 @@ export class RestoreDialog extends Modal {
private createInputBoxHelper(container: Builder, label: string, options?: IInputOptions): InputBox {
let inputBox: InputBox;
let ariaOptions = {
ariaLabel: label
};
container.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(label);
labelContainer.safeInnerHtml(label);
});
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
inputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, options);
inputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, mixin(ariaOptions, options));
});
});
return inputBox;

View File

@@ -0,0 +1,110 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!vs/workbench/parts/extensions/browser/media/extensionEditor';
import { IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Color } from 'vs/base/common/color';
import { append, $, addClass, removeClass, finalHandler, join, toggleClass } from 'vs/base/browser/dom';
import { IInsightTypeContrib } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { IDashboardTabContrib } from 'sql/parts/dashboard/common/dashboardTab.contribution';
import { localize } from 'vs/nls';
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;
}

View File

@@ -94,7 +94,9 @@ export class FileBrowserDialog extends Modal {
tableWrapper.element('table', { class: 'file-table-content' }, (tableContainer) => {
let pathLabel = localize('filebrowser.filepath', 'Selected path');
let pathBuilder = DialogHelper.appendRow(tableContainer, pathLabel, 'file-input-label', 'file-input-box');
this._filePathInputBox = new InputBox(pathBuilder.getHTMLElement(), this._contextViewService);
this._filePathInputBox = new InputBox(pathBuilder.getHTMLElement(), this._contextViewService, {
ariaLabel: pathLabel
});
this._fileFilterSelectBox = new SelectBox(['*'], '*', this._contextViewService);
let filterLabel = localize('fileFilter', 'Files of type');

View File

@@ -17,3 +17,4 @@ export let SelectAllMessages = 'SelectAllMessages';
export let SaveAsCsv = 'SaveAsCSV';
export let SaveAsJSON = 'SaveAsJSON';
export let SaveAsExcel = 'SaveAsExcel';
export let GoToNextQueryOutputTab = 'GoToNextQueryOutputTab';

View File

@@ -24,6 +24,8 @@ export const MESSAGES_SELECTALL_ID = 'grid.messages.selectAll';
export const MESSAGES_COPY_ID = 'grid.messages.copy';
export const TOGGLERESULTS_ID = 'grid.toggleResultPane';
export const TOGGLEMESSAGES_ID = 'grid.toggleMessagePane';
export const GOTONEXTQUERYOUTPUTTAB_ID = 'query.goToNextQueryOutputTab';
export class GridActionProvider {

View File

@@ -53,6 +53,10 @@ export const toggleResultsPane = (accessor: ServicesAccessor) => {
runActionOnActiveResultsEditor(accessor, GridContentEvents.ToggleResultPane);
};
export const goToNextQueryOutputTab = (accessor: ServicesAccessor) => {
runActionOnActiveResultsEditor(accessor, GridContentEvents.GoToNextQueryOutputTab);
};
export const saveAsCsv = (accessor: ServicesAccessor) => {
runActionOnActiveResultsEditor(accessor, GridContentEvents.SaveAsCsv);
};

View File

@@ -23,7 +23,7 @@ import { DataService } from 'sql/parts/grid/services/dataService';
import * as actions from 'sql/parts/grid/views/gridActions';
import * as Services from 'sql/parts/grid/services/sharedServices';
import * as GridContentEvents from 'sql/parts/grid/common/gridContentEvents';
import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext } from 'sql/parts/query/common/queryContext';
import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext, QueryEditorVisibleContext } from 'sql/parts/query/common/queryContext';
import { IBootstrapService } from 'sql/services/bootstrap/bootstrapService';
import { error } from 'sql/base/common/log';
@@ -74,6 +74,7 @@ export abstract class GridParentComponent {
private resultsVisibleContextKey: IContextKey<boolean>;
private gridFocussedContextKey: IContextKey<boolean>;
private messagesFocussedContextKey: IContextKey<boolean>;
private queryEditorVisible: IContextKey<boolean>;
// All datasets
// Place holder data sets to buffer between data sets and rendered data sets
@@ -164,6 +165,9 @@ export abstract class GridParentComponent {
case GridContentEvents.SaveAsExcel:
self.sendSaveRequest(SaveFormat.EXCEL);
break;
case GridContentEvents.GoToNextQueryOutputTab:
self.goToNextQueryOutputTab();
break;
default:
error('Unexpected grid content event type "' + type + '" sent');
break;
@@ -187,6 +191,9 @@ export abstract class GridParentComponent {
private bindKeys(contextKeyService: IContextKeyService): void {
if (contextKeyService) {
this.queryEditorVisible = QueryEditorVisibleContext.bindTo(contextKeyService);
this.queryEditorVisible.set(true);
let gridContextKeyService = this._bootstrapService.contextKeyService.createScoped(this._el.nativeElement);
this.toDispose.push(gridContextKeyService);
this.resultsVisibleContextKey = ResultsVisibleContext.bindTo(gridContextKeyService);
@@ -201,7 +208,7 @@ export abstract class GridParentComponent {
this.toDispose = dispose(this.toDispose);
}
private toggleResultPane(): void {
protected toggleResultPane(): void {
this.resultActive = !this.resultActive;
if (this.resultActive) {
this.resizeGrids();
@@ -209,7 +216,7 @@ export abstract class GridParentComponent {
this._cd.detectChanges();
}
private toggleMessagePane(): void {
protected toggleMessagePane(): void {
this.messageActive = !this.messageActive;
}
@@ -268,6 +275,9 @@ export abstract class GridParentComponent {
return '';
}
protected goToNextQueryOutputTab(): void {
}
private initShortcutsBase(): void {
let shortcuts = {
'ToggleResultPane': () => {
@@ -293,6 +303,9 @@ export abstract class GridParentComponent {
},
'SaveAsExcel': () => {
this.sendSaveRequest(SaveFormat.EXCEL);
},
'GoToNextQueryOutputTab': () => {
this.goToNextQueryOutputTab();
}
};

View File

@@ -8,28 +8,28 @@
</div>
<div class="angular-modal-body-content chart-viewer" style="flex:1 1 auto; border-left: 1px solid; margin: 5px;">
<div style="position: relative; width: 100%">
<div class="dialog-label">{{chartTypeLabel}}</div>
<div class="input-divider" #chartTypesContainer></div>
<div class="dialog-label" id="chartTypeLabel">{{localizedStrings.CHART_TYPE}}</div>
<div class="input-divider" aria-labelledby="chartTypeLabel" #chartTypesContainer></div>
<div [hidden]="chartTypesSelectBox.value === 'count' || chartTypesSelectBox.value === 'image'">
<div [hidden]="!showDataType">
<div class="dialog-label">{{dataTypeLabel}}</div>
<div class="radio-indent">
<div class="dialog-label" id="dataTypeLabel">{{localizedStrings.DATA_TYPE}}</div>
<div class="radio-indent" role="radiogroup" aria-labelledby="dataTypeLabel">
<div class="option">
<input type="radio" name=data-type value="number" [(ngModel)]="dataType">{{numberLabel}}
<input type="radio" role="radio" name="data-type" value="number" [(ngModel)]="dataType" aria-labelledby="numberLabel"><span id="numberLabel">{{localizedStrings.NUMBER}}</span>
</div>
<div class="option">
<input type="radio" name=data-type value="point" [(ngModel)]="dataType">{{pointLabel}}
<input type="radio" role="radio" name="data-type" value="point" [(ngModel)]="dataType" aria-labelledby="pointLabel"><span id="pointLabel">{{localizedStrings.POINT}}</span>
</div>
</div>
</div>
<div [hidden]="!showDataDirection">
<div class="dialog-label">{{dataDirectionLabel}}</div>
<div class="radio-indent">
<div class="dialog-label" id="dataDirectionLabel">{{localizedStrings.DATA_DIRECTION}}</div>
<div class="radio-indent" role="radiogroup" aria-labelledby="dataDirectionLabel">
<div class="option">
<input type="radio" name=data-direction value="vertical" [(ngModel)]="dataDirection">{{verticalLabel}}
<input type="radio" role="radio" name="data-direction" value="vertical" [(ngModel)]="dataDirection" aria-labelledby="verticalLabel"><span id="verticalLabel">{{localizedStrings.VERTICAL}}</span>
</div>
<div class="option">
<input type="radio" name=data-direction value="horizontal" [(ngModel)]="dataDirection">{{horizontalLabel}}
<input type="radio" role="radio" name="data-direction" value="horizontal" [(ngModel)]="dataDirection" aria-labelledby="horizontalLabel"><span id="horizontalLabel">{{localizedStrings.HORIZONTAL}}</span>
</div>
</div>
</div>
@@ -41,8 +41,8 @@
<div class="input-divider" #columnsAsLabelsContainer></div>
</div>
<div class="dialog-label">{{legendLabel}}</div>
<div class="input-divider" #legendContainer></div>
<div class="dialog-label" id="legendLabel">{{localizedStrings.LEGEND}}</div>
<div class="input-divider" #legendContainer aria-labelledby="legendLabel"></div>
<div class="footer">
<div class="right-footer">

View File

@@ -42,6 +42,20 @@ import * as pfs from 'vs/base/node/pfs';
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
const LocalizedStrings = {
CHART_TYPE: nls.localize('chartTypeLabel', 'Chart Type'),
DATA_DIRECTION: nls.localize('dataDirectionLabel', 'Data Direction'),
VERTICAL: nls.localize('verticalLabel', 'Vertical'),
HORIZONTAL: nls.localize('horizontalLabel', 'Horizontal'),
DATA_TYPE: nls.localize('dataTypeLabel', 'Data Type'),
NUMBER: nls.localize('numberLabel', 'Number'),
POINT: nls.localize('pointLabel', 'Point'),
LABEL_FIRST_COLUMN: nls.localize('labelFirstColumnLabel', 'Use First Column as row label?'),
COLUMNS_AS_LABELS: nls.localize('columnsAsLabelsLabel', 'Use Column names as labels?'),
LEGEND: nls.localize('legendLabel', 'Legend Position'),
CHART_NOT_FOUND: nls.localize('chartNotFound', 'Could not find chart to save')
};
@Component({
selector: 'chart-viewer',
templateUrl: decodeURI(require.toUrl('sql/parts/grid/views/query/chartViewer.component.html'))
@@ -54,19 +68,6 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
private columnsAsLabelsCheckBox: Checkbox;
/* UI */
/* tslint:disable:no-unused-variable */
private chartTypeLabel: string = nls.localize('chartTypeLabel', 'Chart Type');
private dataDirectionLabel: string = nls.localize('dataDirectionLabel', 'Data Direction');
private verticalLabel: string = nls.localize('verticalLabel', 'Vertical');
private horizontalLabel: string = nls.localize('horizontalLabel', 'Horizontal');
private dataTypeLabel: string = nls.localize('dataTypeLabel', 'Data Type');
private numberLabel: string = nls.localize('numberLabel', 'Number');
private pointLabel: string = nls.localize('pointLabel', 'Point');
private labelFirstColumnLabel: string = nls.localize('labelFirstColumnLabel', 'Use First Column as row label?');
private columnsAsLabelsLabel: string = nls.localize('columnsAsLabelsLabel', 'Use Column names as labels?');
private legendLabel: string = nls.localize('legendLabel', 'Legend Position');
private chartNotFoundError: string = nls.localize('chartNotFound', 'Could not find chart to save');
/* tslint:enable:no-unused-variable */
private _actionBar: Taskbar;
private _createInsightAction: CreateInsightAction;
@@ -78,6 +79,8 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
private _executeResult: IInsightData;
private _chartComponent: ChartInsight;
private localizedStrings = LocalizedStrings;
@ViewChild(ComponentHostDirective) private componentHost: ComponentHostDirective;
@ViewChild('taskbarContainer', { read: ElementRef }) private taskbarContainer;
@ViewChild('chartTypesContainer', { read: ElementRef }) private chartTypesElement;
@@ -117,15 +120,17 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
// Init label first column checkbox
// Note: must use 'self' for callback
this.labelFirstColumnCheckBox = new Checkbox(this.labelFirstColumnElement.nativeElement, {
label: this.labelFirstColumnLabel,
onChange: () => this.onLabelFirstColumnChanged()
label: LocalizedStrings.LABEL_FIRST_COLUMN,
onChange: () => this.onLabelFirstColumnChanged(),
ariaLabel: LocalizedStrings.LABEL_FIRST_COLUMN
});
// Init label first column checkbox
// Note: must use 'self' for callback
this.columnsAsLabelsCheckBox = new Checkbox(this.columnsAsLabelsElement.nativeElement, {
label: this.columnsAsLabelsLabel,
onChange: () => this.columnsAsLabelsChanged()
label: LocalizedStrings.COLUMNS_AS_LABELS,
onChange: () => this.columnsAsLabelsChanged(),
ariaLabel: LocalizedStrings.COLUMNS_AS_LABELS
});
// Init legend dropdown
@@ -201,7 +206,7 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
public copyChart(): void {
let data = this._chartComponent.getCanvasData();
if (!data) {
this.showError(this.chartNotFoundError);
this.showError(LocalizedStrings.CHART_NOT_FOUND);
return;
}
@@ -212,7 +217,7 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
this.promptForFilepath().then(filePath => {
let data = this._chartComponent.getCanvasData();
if (!data) {
this.showError(this.chartNotFoundError);
this.showError(LocalizedStrings.CHART_NOT_FOUND);
return;
}
if (filePath) {

View File

@@ -6,7 +6,7 @@
-->
<div class="fullsize vertBox" style="position: relative">
<div #resultsPane *ngIf="dataSets.length > 0" id="resultspane" class="boxRow resultsMessageHeader resultsViewCollapsible" [class.collapsed]="!resultActive" (click)="togglePane('results')">
<div #resultsPane role="toolbar" tabindex="0" [attr.aria-label]="LocalizedConstants.resultPaneLabel" [attr.aria-expanded]="resultActive" *ngIf="dataSets.length > 0" id="resultspane" class="boxRow resultsMessageHeader resultsViewCollapsible" [class.collapsed]="!resultActive" (click)="togglePane('results')">
<span> {{LocalizedConstants.resultPaneLabel}} </span>
<span class="queryResultsShortCut"> {{resultShortcut}} </span>
</div>
@@ -41,12 +41,12 @@
</span>
</div>
</div>
<div id="messagepane" class="boxRow resultsMessageHeader resultsViewCollapsible" [class.collapsed]="!messageActive && dataSets.length !== 0" (click)="togglePane('messages')" style="position: relative">
<div id="messagepane" role="toolbar" tabindex="0" [attr.aria-label]="LocalizedConstants.messagePaneLabel" [attr.aria-expanded]="!(!messageActive && dataSets.length !== 0)" class="boxRow resultsMessageHeader resultsViewCollapsible" [class.collapsed]="!messageActive && dataSets.length !== 0" (click)="togglePane('messages')" style="position: relative">
<div id="messageResizeHandle" [class.hidden]="!_resultsPane || !_messageActive || !resultActive" class="resizableHandle"></div>
<span> {{LocalizedConstants.messagePaneLabel}} </span>
<span class="queryResultsShortCut"> {{messageShortcut}} </span>
</div>
<div id="messages" class="scrollable messages" [class.hidden]="!messageActive && dataSets.length !== 0"
<div #messagesContainer id="messages" class="scrollable messages" [class.hidden]="!messageActive && dataSets.length !== 0"
(contextmenu)="openMessagesContextMenu($event)" (focusin)="onMessagesFocus()" (focusout)="onMessagesFocusout()"
tabindex=0>
<div class="messagesTopSpacing"></div>
@@ -58,7 +58,7 @@
<ng-template ngFor let-message [ngForOf]="messages">
<tr class='messageRow'>
<td><span *ngIf="message.link">[{{message.time}}]</span></td>
<td class="resultsMessageValue" [class.errorMessage]="message.isError" [class.batchMessage]="!message.link">{{message.message}} <a class="queryLink" *ngIf="message.link" (click)="onSelectionLinkClicked(message.batchId)">{{message.link.text}}</a>
<td class="resultsMessageValue" [class.errorMessage]="message.isError" [class.batchMessage]="!message.link">{{message.message}} <a tabindex="0" #queryLink class="queryLink" *ngIf="message.link" (click)="onSelectionLinkClicked(message.batchId)" (keyup)="onKey($event, message.batchId)">{{message.link.text}}</a>
</td>
</tr>
</ng-template>

View File

@@ -26,10 +26,12 @@ import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/
import { QueryComponentParams } from 'sql/services/bootstrap/bootstrapParams';
import { error } from 'sql/base/common/log';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { clone } from 'sql/base/common/objects';
import * as strings from 'vs/base/common/strings';
import { clone } from 'sql/base/common/objects';
import * as DOM from 'vs/base/browser/dom';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
export const QUERY_SELECTOR: string = 'query-component';
@@ -144,13 +146,15 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
public queryExecutionStatus: EventEmitter<string> = new EventEmitter<string>();
public queryPlanAvailable: EventEmitter<string> = new EventEmitter<string>();
public showChartRequested: EventEmitter<IGridDataSet> = new EventEmitter<IGridDataSet>();
public goToNextQueryOutputTabRequested: EventEmitter<void> = new EventEmitter<void>();
@Input() public queryParameters: QueryComponentParams;
@ViewChildren('slickgrid') slickgrids: QueryList<SlickGrid>;
// tslint:disable-next-line:no-unused-variable
@ViewChild('resultsPane', { read: ElementRef }) private _resultsPane: ElementRef;
@ViewChild('queryLink', { read: ElementRef }) private _queryLinkElement: ElementRef;
@ViewChild('messagesContainer', { read: ElementRef }) private _messagesContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef,
@@ -404,6 +408,16 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
this.dataService.setEditorSelection(index);
}
onKey(e: Event, index: number) {
if (DOM.isAncestor(<HTMLElement>e.target, this._queryLinkElement.nativeElement) && e instanceof KeyboardEvent) {
let event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter)) {
this.onSelectionLinkClicked(index);
e.stopPropagation();
}
}
}
/**
* Sets up the resize for the messages/results panes bar
*/
@@ -512,7 +526,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
*/
navigateToGrid(targetIndex: number): boolean {
// check if the target index is valid
if (targetIndex >= this.renderedDataSets.length || targetIndex < 0 || !this.hasFocus()) {
if (targetIndex >= this.renderedDataSets.length || !this.hasFocus()) {
return false;
}
@@ -565,16 +579,36 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
}
}
protected goToNextQueryOutputTab(): void {
this.goToNextQueryOutputTabRequested.emit();
}
protected toggleResultPane(): void {
this.resultActive = !this.resultActive;
this._cd.detectChanges();
if (this.resultActive) {
this.resizeGrids();
this.slickgrids.toArray()[this.activeGrid].setActive();
}
}
protected toggleMessagePane(): void {
this.messageActive = !this.messageActive;
this._cd.detectChanges();
if (this.messageActive && this._messagesContainer) {
let header = <HTMLElement>this._messagesContainer.nativeElement;
header.focus();
}
}
/* Helper function to toggle messages and results panes */
// tslint:disable-next-line:no-unused-variable
private togglePane(pane: PaneType): void {
if (pane === 'messages') {
this._messageActive = !this._messageActive;
this.toggleMessagePane();
} else if (pane === 'results') {
this.resultActive = !this.resultActive;
this.toggleResultPane();
}
this._cd.detectChanges();
this.resizeGrids();
}
layout() {

View File

@@ -5,14 +5,14 @@
*--------------------------------------------------------------------------------------------*/
-->
<panel class="fullsize" [options]="panelOpt">
<panel class="dashboard-panel" [options]="panelOpt">
<tab [title]="jobsComponentTitle" class="fullsize" [identifier]="jobsTabIdentifier"
[iconClass]="jobsIconClass">
<div id="jobsDiv" class="fullsize" *ngIf="showHistory === false">
<jobsview-component></jobsview-component>
</div>
<div id="historyDiv" class="fullsize" *ngIf="showHistory === true">
<jobhistory-component [jobId]="jobId" [agentJobInfo]="agentJobInfo" [agentJobHistories]="agentJobHistories"></jobhistory-component>
<jobhistory-component></jobhistory-component>
</div>
</tab>
</panel>

View File

@@ -4,8 +4,10 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!../common/media/jobs';
import 'sql/parts/dashboard/common/dashboardPanelStyles';
import * as nls from 'vs/nls';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable } from '@angular/core';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable} from '@angular/core';
import * as Utils from 'sql/parts/connection/common/utils';
import { RefreshWidgetAction, EditDashboardAction } from 'sql/parts/dashboard/common/actions';
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
@@ -18,6 +20,7 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar
import { AgentJobInfo, AgentJobHistoryInfo } from 'sqlops';
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
export const DASHBOARD_SELECTOR: string = 'agentview-component';
@Component({
@@ -31,14 +34,10 @@ export class AgentViewComponent {
// tslint:disable:no-unused-variable
private readonly jobsComponentTitle: string = nls.localize('jobview.Jobs', "Jobs");
private readonly alertsComponentTitle: string = nls.localize('jobview.Alerts', "Alerts");
private readonly schedulesComponentTitle: string = nls.localize('jobview.Schedules', "Schedules");
private readonly operatorsComponentTitle: string = nls.localize('jobview.Operator', "Operators");
private readonly jobHistoryComponentTitle: string = nls.localize('jobview.History', "History");
private _showHistory: boolean = false;
private _jobId: string = null;
private _agentJobInfo: AgentJobInfo = null;
private _agentJobHistories: AgentJobHistoryInfo[] = null;
private _refresh: boolean = undefined;
public jobsIconClass: string = 'jobsview-icon';
@@ -68,8 +67,8 @@ export class AgentViewComponent {
return this._agentJobInfo;
}
public get agentJobHistories(): AgentJobHistoryInfo[] {
return this._agentJobHistories;
public get refresh(): boolean {
return this._refresh;
}
/**
@@ -91,8 +90,8 @@ export class AgentViewComponent {
this._cd.detectChanges();
}
public set agentJobHistories(value: AgentJobHistoryInfo[]) {
this._agentJobHistories = value;
public set refresh(value: boolean) {
this._refresh = value;
this._cd.detectChanges();
}
}

View File

@@ -1,54 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Injectable } from '@angular/core';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { IAgentJobCacheService } from './interfaces';
@Injectable()
export class AgentJobCacheService implements IAgentJobCacheService {
_serviceBrand: any;
private _jobs: sqlops.AgentJobInfo[];
private _jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = {};
private _prevJobID: string;
/* Getters */
public get jobs(): sqlops.AgentJobInfo[] {
return this._jobs;
}
public get jobHistories(): { [jobId: string]: sqlops.AgentJobHistoryInfo[] } {
return this._jobHistories;
}
public get prevJobID(): string {
return this._prevJobID;
}
public getJobHistory(jobID: string): sqlops.AgentJobHistoryInfo[] {
return this._jobHistories[jobID];
}
/* Setters */
public set jobs(value: sqlops.AgentJobInfo[]) {
this._jobs = value;
}
public set jobHistories(value: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; }) {
this._jobHistories = value;
}
public set prevJobID(value: string) {
this._prevJobID = value;
}
public setJobHistory(jobID:string, value: sqlops.AgentJobHistoryInfo[]) {
this._jobHistories[jobID] = value;
}
}

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
export class AgentJobUtilities {
public static convertToStatusString(status: number): string {
switch(status) {
case(0): return nls.localize('agentUtilities.failed','Failed');
case(1): return nls.localize('agentUtilities.succeeded', 'Succeeded');
case(3): return nls.localize('agentUtilities.canceled', 'Canceled');
case(5): return nls.localize('agentUtilities.statusUnknown', 'Status Unknown');
default: return nls.localize('agentUtilities.statusUnknown', 'Status Unknown');
}
}
public static convertToExecutionStatusString(status: number): string {
switch(status) {
case(1): return nls.localize('agentUtilities.executing', 'Executing');
case(2): return nls.localize('agentUtilities.waitingForThread', 'Waiting for Thread');
case(3): return nls.localize('agentUtilities.betweenRetries', 'Between Retries');
case(4): return nls.localize('agentUtilities.idle', 'Idle');
case(5): return nls.localize('agentUtilities.suspended', 'Suspended');
case(6): return nls.localize('agentUtilities.obsolete', '[Obsolete]');
case(7): return nls.localize('agentUtilities.performingCompletionActions', 'PerformingCompletionActions');
default: return nls.localize('agentUtilities.statusUnknown', 'Status Unknown');
}
}
public static convertToResponse(bool: boolean) {
return bool ? nls.localize('agentUtilities.yes', 'Yes') : nls.localize('agentUtilities.no', 'No');
}
public static convertToNextRun(date: string) {
if (date.includes('1/1/0001')) {
return nls.localize('agentUtilities.notScheduled', 'Not Scheduled');
} else {
return date;
}
}
public static convertToLastRun(date: string) {
if (date.includes('1/1/0001')) {
return nls.localize('agentUtilities.neverRun', 'Never Run');
} else {
return date;
}
}
}

View File

@@ -8,12 +8,11 @@
import * as sqlops from 'sqlops';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Table } from 'sql/base/browser/ui/table/table';
import { JobCacheObject } from './jobManagementService';
export const SERVICE_ID = 'jobManagementService';
export const CACHE_ID = 'jobCacheService';
export const IJobManagementService = createDecorator<IJobManagementService>(SERVICE_ID);
export const IAgentJobCacheService = createDecorator<IAgentJobCacheService>(CACHE_ID);
export interface IJobManagementService {
_serviceBrand: any;
@@ -25,18 +24,8 @@ export interface IJobManagementService {
getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult>;
jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.AgentJobActionResult>;
}
export interface IAgentJobCacheService {
_serviceBrand: any;
addToCache(server: string, cache: JobCacheObject);
jobs: sqlops.AgentJobInfo[];
jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; };
prevJobID: string;
getJobHistory(jobID: string): sqlops.AgentJobHistoryInfo[];
setJobHistory(jobID: string, value: sqlops.AgentJobHistoryInfo[]);
jobCacheObjectMap: { [server: string]: JobCacheObject; };
}

View File

@@ -6,17 +6,20 @@
'use strict';
import { localize } from 'vs/nls';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { TPromise } from 'vs/base/common/winjs.base';
import { Injectable } from '@angular/core';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { TPromise } from 'vs/base/common/winjs.base';
import * as sqlops from 'sqlops';
export class JobManagementService implements IJobManagementService {
_serviceBrand: any;
private _providers: { [handle: string]: sqlops.AgentServicesProvider; } = Object.create(null);
private _jobCacheObject : {[server: string]: JobCacheObject; } = {};
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@@ -60,4 +63,66 @@ export class JobManagementService implements IJobManagementService {
public registerProvider(providerId: string, provider: sqlops.AgentServicesProvider): void {
this._providers[providerId] = provider;
}
public get jobCacheObjectMap(): {[server: string]: JobCacheObject;} {
return this._jobCacheObject;
}
public addToCache(server: string, cacheObject: JobCacheObject) {
this._jobCacheObject[server] = cacheObject;
}
}
/**
* Server level caching of jobs/job histories
*/
export class JobCacheObject {
_serviceBrand: any;
private _jobs: sqlops.AgentJobInfo[] = [];
private _jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = {};
private _prevJobID: string;
private _serverName: string;
/* Getters */
public get jobs(): sqlops.AgentJobInfo[] {
return this._jobs;
}
public get jobHistories(): { [jobId: string]: sqlops.AgentJobHistoryInfo[] } {
return this._jobHistories;
}
public get prevJobID(): string {
return this._prevJobID;
}
public getJobHistory(jobID: string): sqlops.AgentJobHistoryInfo[] {
return this._jobHistories[jobID];
}
public get serverName(): string {
return this._serverName;
}
/* Setters */
public set jobs(value: sqlops.AgentJobInfo[]) {
this._jobs = value;
}
public set jobHistories(value: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; }) {
this._jobHistories = value;
}
public set prevJobID(value: string) {
this._prevJobID = value;
}
public setJobHistory(jobID:string, value: sqlops.AgentJobHistoryInfo[]) {
this._jobHistories[jobID] = value;
}
public set serverName(value: string) {
this._serverName = value;
}
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><defs><style>.cls-1{fill:none;clip-rule:evenodd;}.cls-2{clip-path:url(#clip-path);}.cls-3{fill:#d02e00;}</style><clipPath id="clip-path"><path class="cls-1" d="M8.88,8l2.67-2.67-.88-.88L8,7.12,5.33,4.45l-.88.88L7.12,8,4.45,10.67l.88.88L8,8.88l2.67,2.67.88-.88ZM8,0a7.92,7.92,0,0,1,4,1.09A8.15,8.15,0,0,1,14.91,4a8,8,0,0,1,0,8.07A8.15,8.15,0,0,1,12,14.91a8,8,0,0,1-8.07,0A8.15,8.15,0,0,1,1.09,12,8,8,0,0,1,1.09,4,8.15,8.15,0,0,1,4,1.09,7.92,7.92,0,0,1,8,0Z"/></clipPath></defs><title>failed</title><g class="cls-2"><rect class="cls-3" x="-5" y="-5" width="26" height="26"/></g></svg>

After

Width:  |  Height:  |  Size: 718 B

Some files were not shown because too many files have changed in this diff Show More