Merge remote-tracking branch 'origin/master' into ads-master-vscode-2020-05-31T19-47-47

This commit is contained in:
Anthony Dresser
2020-06-03 12:35:26 -07:00
114 changed files with 2815 additions and 2428 deletions

View File

@@ -52,15 +52,13 @@ jobs:
- run: DISPLAY=:10 ./scripts/test-extensions-unit.sh
name: Run Extension Unit Tests (Electron)
# {{SQL CARBON EDIT}} Add coveralls. We merge first to get around issue where parallel builds weren't being combined correctly
- run: |
mkdir .build/coverage-combined
cat .build/coverage-single/lcov.info ./extensions/admin-tool-ext-win/coverage/lcov.info ./extensions/agent/coverage/lcov.info ./extensions/azurecore/coverage/lcov.info ./extensions/cms/coverage/lcov.info ./extensions/dacpac/coverage/lcov.info ./extensions/schema-compare/coverage/lcov.info ./extensions/notebook/coverage/lcov.info ./extensions/resource-deployment/coverage/lcov.info ./extensions/machine-learning/coverage/lcov.info ./extensions/sql-database-projects/coverage/lcov.info > .build/coverage-combined/lcov.info
name: Merge coverage reports
- run: node test/combineCoverage
name: Combine code coverage files
- name: Upload Code Coverage
uses: coverallsapp/github-action@v1.1.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: '.build/coverage-combined/lcov.info'
path-to-lcov: 'test/coverage/lcov.info'
# Fails with cryptic error (e.g. https://github.com/microsoft/vscode/pull/90292/checks?check_run_id=433681926#step:13:9)
# - run: DISPLAY=:10 yarn test-browser --browser chromium

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -85,7 +85,7 @@
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.3",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
},
"__metadata": {
"id": "41",

View File

@@ -935,9 +935,10 @@ vscode-nls@^3.2.1:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -90,7 +90,7 @@
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.1",
"typemoq": "^2.1.0",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
},
"__metadata": {
"id": "10",

View File

@@ -769,9 +769,10 @@ vscode-nls@^3.2.1:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -146,10 +146,7 @@
{
"command": "azure.resource.startterminal",
"title": "%azure.resource.startterminal.title%",
"icon": {
"dark": "resources/dark/console.svg",
"light": "resources/light/console.svg"
}
"icon": "$(console)"
},
{
"command": "azure.resource.openInAzurePortal",
@@ -288,6 +285,6 @@
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.1",
"typemoq": "^2.1.0",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
}
}

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 1H15V15H1V1ZM2 14H14V2H2V14ZM4.00008 5.70709L4.70718 4.99999L8.24272 8.53552L7.53561 9.24263L7.53558 9.2426L4.70711 12.0711L4 11.364L6.82848 8.53549L4.00008 5.70709Z" fill="#C5C5C5"/>
</svg>

Before

Width:  |  Height:  |  Size: 339 B

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 1H15V15H1V1ZM2 14H14V2H2V14ZM4.00008 5.70709L4.70718 4.99999L8.24272 8.53552L7.53561 9.24263L7.53558 9.2426L4.70711 12.0711L4 11.364L6.82848 8.53549L4.00008 5.70709Z" fill="#424242"/>
</svg>

Before

Width:  |  Height:  |  Size: 339 B

View File

@@ -1331,9 +1331,10 @@ vscode-nls@^4.0.0:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -657,7 +657,7 @@
"mocha-multi-reporters": "^1.1.7",
"mocha": "^5.2.0",
"should": "^13.2.1",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6",
"vscodetestcover": "^1.0.9",
"typemoq": "^2.1.0"
},
"__metadata": {

View File

@@ -1088,9 +1088,10 @@ vscode-nls@^4.0.0:
resolved "https://registry.npmjs.org/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002"
integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -5,7 +5,6 @@
"ignorePatterns": [
"**/node_modules/**"
],
"includePid": false,
"reports": [
"cobertura"
],

View File

@@ -62,7 +62,7 @@
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.1",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
},
"__metadata": {
"id": "33",

View File

@@ -816,9 +816,10 @@ vscode-nls@^4.0.0:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"
integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -0,0 +1,21 @@
{
"enabled": true,
"relativeSourcePath": "../../../",
"relativeCoverageDir": "../../coverage",
"ignorePatterns": [
"**/coverage/**",
"**/generated/**",
"**/node_modules/**",
"**/test/**"
],
"reports": [
"cobertura",
"lcov",
"json"
],
"verbose": false,
"remapOptions": {
"basePath": "../../../",
"useAbsolutePaths": true
}
}

View File

@@ -22,8 +22,7 @@
"Microsoft.azuredatastudio-postgresql"
],
"scripts": {
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json",
"postinstall": "node ./node_modules/vscode/bin/install"
"vscode:prepublish": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:vscode-colorize-tests ./tsconfig.json"
},
"devDependencies": {
"@types/chai": "3.4.34",
@@ -33,6 +32,6 @@
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"ms-rest-azure": "^2.6.0",
"vscode": "1.1.5"
"vscodetestcover": "^1.0.9"
}
}

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as testRunner from 'vscodetestcover';
const suite = 'Extension Integration Tests';
const mochaOptions: any = {
ui: 'tdd',
useColors: true,
timeout: 60000
};
// set relevant mocha options from the environment
if (process.env.ADS_TEST_GREP) {
mochaOptions.grep = process.env.ADS_TEST_GREP;
console.log(`setting options.grep to: ${mochaOptions.grep}`);
}
if (process.env.ADS_TEST_INVERT_GREP) {
mochaOptions.invert = parseInt(process.env.ADS_TEST_INVERT_GREP);
console.log(`setting options.invert to: ${mochaOptions.invert}`);
}
if (process.env.ADS_TEST_TIMEOUT) {
mochaOptions.timeout = parseInt(process.env.ADS_TEST_TIMEOUT);
console.log(`setting options.timeout to: ${mochaOptions.timeout}`);
}
if (process.env.ADS_TEST_RETRIES) {
mochaOptions.retries = parseInt(process.env.ADS_TEST_RETRIES);
console.log(`setting options.retries to: ${mochaOptions.retries}`);
}
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
mochaOptions.reporter = 'mocha-multi-reporters';
mochaOptions.reporterOptions = {
reporterEnabled: 'spec, mocha-junit-reporter',
mochaJunitReporterReporterOptions: {
testsuitesTitle: `${suite} ${process.platform}`,
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
}
};
}
testRunner.configure(mochaOptions, { coverConfig: '../../coverConfig.json' });
export = testRunner;

View File

@@ -84,7 +84,7 @@ suite('Notebook integration test suite', function () {
}
});
test('Sql NB run cells above and below test', async function () {
test('Sql NB run cells above and below test @UNSTABLE@', async function () {
let notebook = await openNotebook(sqlNotebookMultipleCellsContent, sqlKernelMetadata, this.test.title + this.invocationCount++);
// When running all cells above a cell, ensure that only cells preceding current cell have output
await runCells(notebook, true, undefined, notebook.document.cells[1]);
@@ -101,13 +101,13 @@ suite('Notebook integration test suite', function () {
assert(notebook.document.cells[2].contents.outputs.length === 3, `Expected length: '3', Actual: '${notebook.document.cells[2].contents.outputs.length}'`);
});
test('Clear cell output - SQL notebook', async function () {
test('Clear cell output - SQL notebook @UNSTABLE@', async function () {
let notebook = await openNotebook(sqlNotebookContent, sqlKernelMetadata, this.test.title + this.invocationCount++);
await runCell(notebook);
await verifyClearOutputs(notebook);
});
test('Clear all outputs - SQL notebook ', async function () {
test('Clear all outputs - SQL notebook @UNSTABLE@', async function () {
let notebook = await openNotebook(sqlNotebookContent, sqlKernelMetadata, this.test.title + this.invocationCount++);
await runCell(notebook);
await verifyClearAllOutputs(notebook);

View File

@@ -1,50 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as testRunner from 'vscode/lib/testrunner';
import * as path from 'path';
const suite = 'Extension Integration Tests';
const options: any = {
ui: 'tdd',
useColors: true,
timeout: 600000
};
// set relevant mocha options from the environment
if (process.env.ADS_TEST_GREP) {
options.grep = process.env.ADS_TEST_GREP;
console.log(`setting options.grep to: ${options.grep}`);
}
if (process.env.ADS_TEST_INVERT_GREP) {
const value = parseInt(process.env.ADS_TEST_INVERT_GREP);
options.invert = Boolean(value);
console.log(`setting options.invert to: ${options.invert}`);
}
if (process.env.ADS_TEST_TIMEOUT) {
options.timeout = parseInt(process.env.ADS_TEST_TIMEOUT);
console.log(`setting options.timeout to: ${options.timeout}`);
}
if (process.env.ADS_TEST_RETRIES) {
options.retries = parseInt(process.env.ADS_TEST_RETRIES);
console.log(`setting options.retries to: ${options.retries}`);
}
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
console.log(`environment variable BUILD_ARTIFACTSTAGINGDIRECTORY is set to ${process.env.BUILD_ARTIFACTSTAGINGDIRECTORY} so configuring multiple reporters for test results.\n For this to work the ${process.env.BUILD_ARTIFACTSTAGINGDIRECTORY} must be fully qualified directory and must exist`);
options.reporter = 'mocha-multi-reporters';
options.reporterOptions = {
reporterEnabled: 'spec, mocha-junit-reporter',
mochaJunitReporterReporterOptions: {
testsuitesTitle: `${suite} Tests ${process.platform}`,
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
}
};
}
testRunner.configure(options);
export = testRunner;

View File

@@ -5,4 +5,5 @@
/// <reference path='../../../../src/sql/azdata.d.ts'/>
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference path='../../../../src/sql/azdata.test.d.ts'/>
/// <reference path='../../../../src/vs/vscode.d.ts'/>
/// <reference types='@types/node'/>

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@
"requiredPythonPackages": [
{
"name": "sqlmlutils",
"version": "1.0.0"
"version": "1.0.1"
}
],
"requiredRPackages": [

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -148,7 +148,7 @@
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.1",
"typemoq": "^2.1.0",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
},
"__metadata": {
"id": "65",

View File

@@ -67,13 +67,14 @@ export class SqlPythonPackageManageProvider extends SqlPackageManageProviderBase
let port = '1433';
let server = connection.serverName;
let database = databaseName ? `, database="${databaseName}"` : '';
const auth = connection.userName ? `, uid="${connection.userName}", pwd="${credentials[azdata.ConnectionOptionSpecialType.password]}"` : '';
let index = connection.serverName.indexOf(',');
if (index > 0) {
port = connection.serverName.substring(index + 1);
server = connection.serverName.substring(0, index);
}
let pythonConnectionParts = `server="${server}", port=${port}, uid="${connection.userName}", pwd="${credentials[azdata.ConnectionOptionSpecialType.password]}"${database})`;
let pythonConnectionParts = `server="${server}", port=${port}${auth}${database})`;
let pythonCommandScript = scriptMode === ScriptMode.Install ?
`pkgmanager.install(package="${packageDetails.name}", version="${packageDetails.version}")` :
`pkgmanager.uninstall(package_name="${packageDetails.name}")`;

View File

@@ -83,7 +83,7 @@ export class PredictService {
language: 'sql',
content: query
});
await this._apiWrapper.showTextDocument(document.uri);
await this._apiWrapper.executeCommand('vscode.open', document.uri);
await this._apiWrapper.connect(document.uri.toString(), connection.connectionId);
this._apiWrapper.runQuery(document.uri.toString(), undefined, false);
return query;

View File

@@ -132,6 +132,7 @@ describe('SQL Python Package Manager', () => {
let connection = new azdata.connection.ConnectionProfile();
connection.serverName = 'serverName';
connection.databaseName = 'databaseName';
connection.userName = 'user';
let credentials = { [azdata.ConnectionOptionSpecialType.password]: 'password' };
testContext.apiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return Promise.resolve(connection); });
testContext.apiWrapper.setup(x => x.getCredentials(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(credentials); });
@@ -173,6 +174,7 @@ describe('SQL Python Package Manager', () => {
let connection = new azdata.connection.ConnectionProfile();
connection.serverName = 'serverName';
connection.databaseName = 'databaseName';
connection.userName = 'user';
let credentials = { [azdata.ConnectionOptionSpecialType.password]: 'password' };
testContext.apiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return Promise.resolve(connection); });
testContext.apiWrapper.setup(x => x.getCredentials(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(credentials); });
@@ -213,6 +215,7 @@ describe('SQL Python Package Manager', () => {
let connection = new azdata.connection.ConnectionProfile();
connection.serverName = 'serverName,3433';
connection.databaseName = 'databaseName';
connection.userName = 'user';
let credentials = { [azdata.ConnectionOptionSpecialType.password]: 'password' };
testContext.apiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return Promise.resolve(connection); });
testContext.apiWrapper.setup(x => x.getCredentials(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(credentials); });
@@ -237,6 +240,88 @@ describe('SQL Python Package Manager', () => {
should.deepEqual(packagesUpdated, true);
});
it('installPackages Should not include credential for windows auth', async function (): Promise<void> {
let testContext = createContext();
let packagesUpdated = false;
let packages: nbExtensionApis.IPackageDetails[] = [
{
'name': 'a-name',
'version': '1.1.2'
},
{
'name': 'b-name',
'version': '1.1.1'
}
];
let connection = new azdata.connection.ConnectionProfile();
connection.serverName = 'serverName,3433';
connection.databaseName = 'databaseName';
let credentials = { [azdata.ConnectionOptionSpecialType.password]: 'password' };
testContext.apiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return Promise.resolve(connection); });
testContext.apiWrapper.setup(x => x.getCredentials(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(credentials); });
testContext.processService.setup(x => x.execScripts(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((path, scripts: string[]) => {
if (path && scripts.find(x => x.indexOf('install') > 0) &&
scripts.find(x => x.indexOf('port=3433') > 0) &&
scripts.find(x => x.indexOf('server="serverName"') > 0) &&
scripts.find(x => x.indexOf('database="databaseName"') > 0) &&
scripts.find(x => x.indexOf('package="a-name"') > 0) &&
scripts.find(x => x.indexOf('version="1.1.2"') > 0) &&
scripts.find(x => x.indexOf('pwd="password"') < 0)) {
packagesUpdated = true;
}
return Promise.resolve('');
});
let provider = createProvider(testContext);
await provider.installPackages(packages, false, connection.databaseName);
should.deepEqual(packagesUpdated, true);
});
it('installPackages Should not include database if not specified', async function (): Promise<void> {
let testContext = createContext();
let packagesUpdated = false;
let packages: nbExtensionApis.IPackageDetails[] = [
{
'name': 'a-name',
'version': '1.1.2'
},
{
'name': 'b-name',
'version': '1.1.1'
}
];
let connection = new azdata.connection.ConnectionProfile();
connection.serverName = 'serverName,3433';
connection.databaseName = '';
let credentials = { [azdata.ConnectionOptionSpecialType.password]: 'password' };
testContext.apiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return Promise.resolve(connection); });
testContext.apiWrapper.setup(x => x.getCredentials(TypeMoq.It.isAny())).returns(() => { return Promise.resolve(credentials); });
testContext.processService.setup(x => x.execScripts(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((path, scripts: string[]) => {
if (path && scripts.find(x => x.indexOf('install') > 0) &&
scripts.find(x => x.indexOf('port=3433') > 0) &&
scripts.find(x => x.indexOf('server="serverName"') > 0) &&
scripts.find(x => x.indexOf('database="databaseName"') < 0) &&
scripts.find(x => x.indexOf('package="a-name"') > 0) &&
scripts.find(x => x.indexOf('version="1.1.2"') > 0) &&
scripts.find(x => x.indexOf('pwd="password"') < 0)) {
packagesUpdated = true;
}
return Promise.resolve('');
});
let provider = createProvider(testContext);
await provider.installPackages(packages, false, connection.databaseName);
should.deepEqual(packagesUpdated, true);
});
it('installPackages Should not install any packages give empty list', async function (): Promise<void> {
let testContext = createContext();
let packagesUpdated = false;

View File

@@ -1,19 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import 'mocha';
import { createContext } from './utils';
import { LanguagesDialog } from '../../../views/externalLanguages/languagesDialog';
describe('External Languages Dialog', () => {
it('Should open dialog successfully ', async function (): Promise<void> {
let testContext = createContext();
let dialog = new LanguagesDialog(testContext.apiWrapper.object, '');
dialog.showDialog();
should.notEqual(dialog.addNewLanguageTab, undefined);
should.notEqual(dialog.currentLanguagesTab, undefined);
});
});

View File

@@ -1242,9 +1242,10 @@ vscode-nls@^4.0.0:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"
integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -1110,6 +1110,6 @@
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"typemoq": "^2.1.0",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -339,6 +339,12 @@
"when": "false"
}
],
"notebooks/title": [
{
"command": "notebook.command.createBook",
"group": "secondary"
}
],
"touchBar": [
{
"command": "notebook.command.runactivecell",
@@ -376,12 +382,12 @@
},
{
"command": "notebook.command.searchUntitledBook",
"when": "view == unsavedBookTreeView && viewItem == unsavedBook && unsavedBooks",
"when": "view == providedBooksView && viewItem == providedBook && providedBooks",
"group": "inline"
},
{
"command": "notebook.command.saveBook",
"when": "view == unsavedBookTreeView && viewItem == unsavedBook && unsavedBooks",
"when": "view == providedBooksView && viewItem == providedBook && providedBooks",
"group": "inline"
},
{
@@ -400,8 +406,8 @@
"group": "navigation"
},
{
"command": "notebook.command.createBook",
"when": "view == bookTreeView"
"command": "books.sqlserver2019",
"when": "view == providedBooksView"
},
{
"command": "notebook.command.openNotebookFolder",
@@ -416,6 +422,18 @@
}
]
},
"views": {
"notebooks": [
{
"id": "bookTreeView",
"name": "%title.SavedBooks%"
},
{
"id": "providedBooksView",
"name": "%title.ProvidedBooks%"
}
]
},
"keybindings": [
{
"command": "notebook.command.runactivecell",
@@ -507,27 +525,6 @@
"connectionProviderIds": []
}
]
},
"viewsContainers": {
"activitybar": [
{
"id": "books-explorer",
"title": "Notebooks",
"icon": "resources/dark/JupyterBook_2.svg"
}
]
},
"views": {
"books-explorer": [
{
"id": "bookTreeView",
"name": "%title.SavedBooks%"
},
{
"id": "unsavedBookTreeView",
"name": "%title.UnsavedBooks%"
}
]
}
},
"dependencies": {
@@ -564,7 +561,7 @@
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.3",
"typemoq": "^2.1.0",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
},
"enableProposedApi": true
}

View File

@@ -34,7 +34,7 @@
"title.trustBook": "Trust Book",
"title.searchJupyterBook": "Search Book",
"title.SavedBooks": "Notebooks",
"title.UnsavedBooks": "Provided Books",
"title.ProvidedBooks": "Provided Books",
"title.PreviewLocalizedBook": "Get localized SQL Server 2019 guide",
"title.openJupyterBook": "Open Jupyter Book",
"title.closeJupyterBook": "Close Jupyter Book",

View File

@@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<title>ADS_jupyterBook</title>
<path d="M4.25,24A2.251,2.251,0,0,1,2,21.75V3.86A2.257,2.257,0,0,1,3.962,1.628L16.462.019A2.251,2.251,0,0,1,19,2.25H17.5a.741.741,0,0,0-.255-.563.755.755,0,0,0-.591-.181l-12.5,1.61A.752.752,0,0,0,3.5,3.86V21.75a.738.738,0,0,0,.255.563.746.746,0,0,0,.59.181l12.5-1.61a.753.753,0,0,0,.655-.744V2.263H19V20.14a2.257,2.257,0,0,1-1.963,2.232l-12.5,1.609A2.139,2.139,0,0,1,4.25,24Z" fill="#fff"/>
<path d="M19.75,24H4.5a.75.75,0,0,1,0-1.5H19.75a.75.75,0,0,0,.75-.75V3.763a.75.75,0,0,0-.75-.75h-1.5a.75.75,0,0,1,0-1.5h1.5A2.252,2.252,0,0,1,22,3.763V21.75A2.252,2.252,0,0,1,19.75,24Z" fill="#fff"/>
<rect width="24" height="24" fill="none"/>
<path d="M7.645,10.966A1.615,1.615,0,0,1,6.022,9.351v-1.8a1.619,1.619,0,0,1,1.411-1.6l5.809-.927a1.615,1.615,0,0,1,1.845,1.6v1.8a1.62,1.62,0,0,1-1.41,1.6l-5.809.927A1.693,1.693,0,0,1,7.645,10.966ZM13.36,5.757l.119.741-5.833.93c-.08.011-.124.06-.124.119v1.8l.228.859-.118-.74,5.831-.93a.126.126,0,0,0,.124-.119v-1.8Z" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -41,7 +41,7 @@ export class BookTreeItem extends vscode.TreeItem {
this.collapsibleState = book.treeItemCollapsibleState;
this._sections = book.page;
if (book.isUntitled) {
this.contextValue = 'unsavedBook';
this.contextValue = 'providedBook';
} else {
this.contextValue = 'savedBook';
}

View File

@@ -54,7 +54,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
}
private async initialize(workspaceFolders: vscode.WorkspaceFolder[]): Promise<void> {
await vscode.commands.executeCommand('setContext', 'unsavedBooks', this._openAsUntitled);
await vscode.commands.executeCommand('setContext', 'providedBooks', this._openAsUntitled);
await Promise.all(workspaceFolders.map(async (workspaceFolder) => {
try {
await this.loadNotebooksInFolder(workspaceFolder.uri.fsPath);

View File

@@ -24,21 +24,21 @@ const JUPYTER_NOTEBOOK_PROVIDER = 'jupyter';
const msgSampleCodeDataFrame = localize('msgSampleCodeDataFrame', "This sample code loads the file into a data frame and shows the first 10 results.");
const noNotebookVisible = localize('noNotebookVisible', "No notebook editor is active");
const BOOKS_VIEWID = 'bookTreeView';
const READONLY_BOOKS_VIEWID = 'unsavedBookTreeView';
const PROVIDED_BOOKS_VIEWID = 'providedBooksView';
let controller: JupyterController;
type ChooseCellType = { label: string, id: CellType };
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtensionApi> {
const createBookPath: string = path.posix.join(extensionContext.extensionPath, 'resources', 'notebooks', 'JupyterBooksCreate.ipynb');
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openBook', (bookPath: string, openAsUntitled: boolean, urlToOpen?: string) => openAsUntitled ? untitledBookTreeViewProvider.openBook(bookPath, urlToOpen, true) : bookTreeViewProvider.openBook(bookPath, urlToOpen, true)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openBook', (bookPath: string, openAsUntitled: boolean, urlToOpen?: string) => openAsUntitled ? providedBookTreeViewProvider.openBook(bookPath, urlToOpen, true) : bookTreeViewProvider.openBook(bookPath, urlToOpen, true)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openNotebook', (resource) => bookTreeViewProvider.openNotebook(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openUntitledNotebook', (resource) => untitledBookTreeViewProvider.openNotebookAsUntitled(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openUntitledNotebook', (resource) => providedBookTreeViewProvider.openNotebookAsUntitled(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openMarkdown', (resource) => bookTreeViewProvider.openMarkdown(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openExternalLink', (resource) => bookTreeViewProvider.openExternalLink(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.saveBook', () => untitledBookTreeViewProvider.saveJupyterBooks()));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.saveBook', () => providedBookTreeViewProvider.saveJupyterBooks()));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.trustBook', (resource) => bookTreeViewProvider.trustBook(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchBook', (item) => bookTreeViewProvider.searchJupyterBooks(item)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchUntitledBook', () => untitledBookTreeViewProvider.searchJupyterBooks()));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchUntitledBook', () => providedBookTreeViewProvider.searchJupyterBooks()));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.openBook', () => bookTreeViewProvider.openNewBook()));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.closeBook', (book: any) => bookTreeViewProvider.closeBook(book)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.closeNotebook', (book: any) => bookTreeViewProvider.closeBook(book)));
@@ -114,7 +114,7 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
}));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.revealInBooksViewlet', (uri: vscode.Uri, shouldReveal: boolean) => bookTreeViewProvider.revealActiveDocumentInViewlet(uri, shouldReveal)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.revealInUntitledBooksViewlet', (uri: vscode.Uri, shouldReveal: boolean) => untitledBookTreeViewProvider.revealActiveDocumentInViewlet(uri, shouldReveal)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.revealInUntitledBooksViewlet', (uri: vscode.Uri, shouldReveal: boolean) => providedBookTreeViewProvider.revealActiveDocumentInViewlet(uri, shouldReveal)));
let appContext = new AppContext(extensionContext, new ApiWrapper());
controller = new JupyterController(appContext);
@@ -126,9 +126,12 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
let workspaceFolders = vscode.workspace.workspaceFolders?.slice() ?? [];
const bookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, workspaceFolders, extensionContext, false, BOOKS_VIEWID);
await bookTreeViewProvider.initialized;
const untitledBookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], extensionContext, true, READONLY_BOOKS_VIEWID);
await untitledBookTreeViewProvider.initialized;
const providedBookTreeViewProvider = new BookTreeViewProvider(appContext.apiWrapper, [], extensionContext, true, PROVIDED_BOOKS_VIEWID);
await providedBookTreeViewProvider.initialized;
extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider(BOOKS_VIEWID, bookTreeViewProvider));
extensionContext.subscriptions.push(vscode.window.registerTreeDataProvider(PROVIDED_BOOKS_VIEWID, providedBookTreeViewProvider));
return {
getJupyterController() {
return controller;

View File

@@ -282,6 +282,10 @@ export class JupyterSession implements nb.ISession {
// %_do_not_call_change_endpoint is a SparkMagic command that lets users change endpoint options,
// such as user/profile/host name/auth type
let credentials;
if (!this.isIntegratedAuth(connectionProfile)) {
credentials = await connection.getCredentials(connectionProfile.id);
}
//Update server info with bigdata endpoint - Unified Connection
if (connectionProfile.providerName === SQL_PROVIDER) {
const endpoints = await this.getClusterEndpoints(connectionProfile.id);
@@ -296,16 +300,16 @@ export class JupyterSession implements nb.ISession {
// as a default now we'll still fall back to root if it's empty for some reason. (but the calls below should
// get the actual correct value regardless)
connectionProfile.options[USER] = connectionProfile.userName || 'root';
if (!this.isIntegratedAuth(connectionProfile)) {
try {
const bdcApi = <bdc.IExtension>await vscode.extensions.getExtension(bdc.constants.extensionName).activate();
const controllerEndpoint = endpoints.find(ep => ep.serviceName.toLowerCase() === CONTROLLER_ENDPOINT);
const controller = bdcApi.getClusterController(controllerEndpoint.endpoint, 'basic', connectionProfile.userName, connectionProfile.password);
const controller = bdcApi.getClusterController(controllerEndpoint.endpoint, 'basic', connectionProfile.userName, credentials.password);
connectionProfile.options[USER] = await controller.getKnoxUsername(connectionProfile.userName);
} catch (err) {
console.log(`Unexpected error getting Knox username for Spark kernel: ${err}`);
}
}
}
else {
connectionProfile.options[KNOX_ENDPOINT_PORT] = this.getKnoxPortOrDefault(connectionProfile);
@@ -318,7 +322,7 @@ export class JupyterSession implements nb.ISession {
if (this.isIntegratedAuth(connectionProfile)) {
doNotCallChangeEndpointParams = `%_do_not_call_change_endpoint --server=${server} --auth=Kerberos`;
} else {
const credentials = await connection.getCredentials(connectionProfile.id);
doNotCallChangeEndpointParams = `%_do_not_call_change_endpoint --username=${connectionProfile.options[USER]} --password=${credentials.password} --server=${server} --auth=Basic_Access`;
}
let future = this.sessionImpl.kernel.requestExecute({

View File

@@ -1661,9 +1661,10 @@ vscode-nls@^4.0.0:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002"
integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -1,17 +0,0 @@
{
"enabled": true,
"relativeSourcePath": "..",
"relativeCoverageDir": "../../coverage",
"ignorePatterns": [
"**/node_modules/**"
],
"includePid": false,
"reports": [
"cobertura"
],
"verbose": false,
"remapOptions": {
"basePath": ".",
"useAbsolutePaths": true
}
}

View File

@@ -362,6 +362,6 @@
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"typemoq": "^2.1.0",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
}
}

View File

@@ -42,7 +42,7 @@ export interface DialogDeploymentProvider extends DeploymentProviderBase {
}
export interface BdcWizardDeploymentProvider extends DeploymentProviderBase {
bdcWizard: WizardInfo;
bdcWizard: BdcWizardInfo;
}
export interface NotebookWizardDeploymentProvider extends DeploymentProviderBase {
@@ -50,7 +50,7 @@ export interface NotebookWizardDeploymentProvider extends DeploymentProviderBase
}
export interface NotebookDeploymentProvider extends DeploymentProviderBase {
notebook: string | NotebookInfo;
notebook: string | NotebookPathInfo;
}
export interface WebPageDeploymentProvider extends DeploymentProviderBase {
@@ -100,31 +100,32 @@ export interface DeploymentProviderBase {
export type DeploymentProvider = DialogDeploymentProvider | BdcWizardDeploymentProvider | NotebookWizardDeploymentProvider | NotebookDeploymentProvider | WebPageDeploymentProvider | DownloadDeploymentProvider | CommandDeploymentProvider;
export interface WizardInfo {
notebook: string | NotebookInfo;
export interface BdcWizardInfo {
notebook: string | NotebookPathInfo;
type: BdcDeploymentType;
}
export interface NotebookWizardInfo extends WizardInfoBase {
notebook: string | NotebookInfo;
notebook: string | NotebookPathInfo;
runNotebook?: boolean;
codeCellInsertionPosition?: number;
pages: NotebookWizardPageInfo[]
}
export interface WizardInfoBase extends SharedFieldAttributes {
export interface WizardInfoBase extends FieldInfoBase {
taskName?: string;
type?: DeploymentType;
runNotebook?: boolean;
actionText?: string;
title: string;
pages: NotebookWizardPageInfo[];
summaryPage: NotebookWizardPageInfo;
generateSummaryPage: boolean;
pages: PageInfoBase[];
isSummaryPageAutoGenerated?: boolean
}
export interface NotebookWizardPageInfo extends PageInfoBase {
description?: string;
}
export interface NotebookBasedDialogInfo extends DialogInfoBase {
notebook: string | NotebookInfo;
notebook: string | NotebookPathInfo;
runNotebook?: boolean;
taskName?: string;
}
@@ -153,18 +154,39 @@ export interface DialogInfoBase {
export interface DialogTabInfo extends PageInfoBase {
}
export interface PageInfoBase extends SharedFieldAttributes {
export interface PageInfoBase extends FieldInfoBase {
title: string;
isSummaryPage?: boolean;
sections: SectionInfo[];
}
export interface SharedFieldAttributes {
export interface TextCSSStyles {
fontStyle?: FontStyle | undefined;
fontWeight?: FontWeight | undefined;
color?: string;
[key: string]: string | undefined;
}
export type ComponentCSSStyles = {
[key: string]: string;
};
export interface OptionsInfo {
values: string[] | azdata.CategoryValue[],
defaultValue: string,
optionsType?: OptionsType
}
export interface FieldInfoBase {
labelWidth?: string;
inputWidth?: string;
labelPosition?: LabelPosition; // Default value is top
fieldWidth?: string;
fieldHeight?: string;
fieldAlignItems?: azdata.AlignItemsType;
}
export interface SectionInfo extends SharedFieldAttributes {
export interface SectionInfo extends FieldInfoBase {
title?: string;
fields?: FieldInfo[]; // Use this if the dialog is not wide. All fields will be displayed in one column, label will be placed on top of the input component.
rows?: RowInfo[]; // Use this for wide dialog or wizard. label will be placed to the left of the input component.
@@ -174,7 +196,8 @@ export interface SectionInfo extends SharedFieldAttributes {
}
export interface RowInfo {
fields: FieldInfo[];
cssStyles?: ComponentCSSStyles;
items: FieldInfo[] | RowInfo[];
}
export interface SubFieldInfo {
@@ -182,7 +205,7 @@ export interface SubFieldInfo {
variableName?: string;
}
export interface FieldInfo extends SubFieldInfo, SharedFieldAttributes {
export interface FieldInfo extends SubFieldInfo, FieldInfoBase {
subFields?: SubFieldInfo[];
type: FieldType;
defaultValue?: string;
@@ -194,22 +217,23 @@ export interface FieldInfo extends SubFieldInfo, SharedFieldAttributes {
min?: number;
max?: number;
required?: boolean;
options?: string[] | azdata.CategoryValue[];
options?: string[] | azdata.CategoryValue[] | OptionsInfo;
placeHolder?: string;
userName?: string; // needed for sql server's password complexity requirement check, password can not include the login name.
description?: string;
fontStyle?: FontStyle;
labelFontWeight?: FontWeight;
textFontWeight?: FontWeight;
labelCSSStyles?: TextCSSStyles;
fontWeight?: FontWeight;
links?: azdata.LinkArea[];
editable?: boolean; // for editable drop-down,
enabled?: boolean;
isEvaluated?: boolean;
}
export interface KubeClusterContextFieldInfo extends FieldInfo {
configFileVariableName?: string;
}
export interface AzureAccountFieldInfo extends AzureLocationsFieldInfo {
displaySubscriptionVariableName?: string;
subscriptionVariableName?: string;
resourceGroupVariableName?: string;
}
@@ -242,16 +266,20 @@ export enum FieldType {
SQLPassword = 'sql_password',
Password = 'password',
Options = 'options',
RadioOptions = 'radio_options',
ReadonlyText = 'readonly_text',
Checkbox = 'checkbox',
AzureAccount = 'azure_account',
AzureLocations = 'azure_locations',
FilePicker = 'file_picker',
KubeClusterContextPicker = 'kube_cluster_context_picker'
KubeClusterContextPicker = 'kube_cluster_context_picker',
}
export interface NotebookInfo {
export enum OptionsType {
Dropdown = 'dropdown',
Radio = 'radio'
}
export interface NotebookPathInfo {
win32: string;
darwin: string;
linux: string;

View File

@@ -9,7 +9,7 @@ import * as path from 'path';
import { isString } from 'util';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { NotebookInfo } from '../interfaces';
import { NotebookPathInfo } from '../interfaces';
import { getDateTimeString, getErrorMessage } from '../utils';
import { IPlatformService } from './platformService';
const localize = nls.loadMessageBundle();
@@ -33,11 +33,13 @@ export interface NotebookExecutionResult {
}
export interface INotebookService {
launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor>;
launchNotebookWithContent(title: string, content: string): Thenable<azdata.nb.NotebookEditor>;
getNotebook(notebook: string | NotebookInfo): Promise<Notebook>;
launchNotebook(notebook: string | NotebookPathInfo): Promise<azdata.nb.NotebookEditor>;
launchNotebookWithEdits(notebook: string | NotebookPathInfo, cellStatements: string[], insertionPosition?: number): Promise<void>;
launchNotebookWithContent(title: string, content: string): Promise<azdata.nb.NotebookEditor>;
getNotebook(notebook: string | NotebookPathInfo): Promise<Notebook>;
getNotebookPath(notebook: string | NotebookPathInfo): string;
executeNotebook(notebook: any, env?: NodeJS.ProcessEnv): Promise<NotebookExecutionResult>;
backgroundExecuteNotebook(taskName: string | undefined, notebookInfo: string | NotebookInfo, tempNoteBookPrefix: string, platformService: IPlatformService): void;
backgroundExecuteNotebook(taskName: string | undefined, notebookInfo: string | NotebookPathInfo | Notebook, tempNotebookPrefix: string, platformService: IPlatformService, env?: NodeJS.ProcessEnv): void;
}
export class NotebookService implements INotebookService {
@@ -48,9 +50,26 @@ export class NotebookService implements INotebookService {
* Launch notebook with file path
* @param notebook the path of the notebook
*/
launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor> {
return this.getNotebookFullPath(notebook).then(notebookPath => {
return this.showNotebookAsUntitled(notebookPath);
async launchNotebook(notebook: string | NotebookPathInfo): Promise<azdata.nb.NotebookEditor> {
const notebookPath = await this.getNotebookFullPath(notebook);
return await this.showNotebookAsUntitled(notebookPath);
}
/**
* Inserts cell code given by {@param cellStatements} in an existing notebook given by {@param notebook} file path at the location
* {@param insertionPosition} and then launches the edited notebook.
*
* @param notebook - the path to notebook that needs to be launched
* @param cellStatements - array of statements to be inserted in a cell
* @param insertionPosition - the position at which cells are inserted. Default is a new cell at the beginning of the notebook.
*/
async launchNotebookWithEdits(notebook: string, cellStatements: string[], insertionPosition: number = 0): Promise<void> {
const openedNotebook = await this.launchNotebook(notebook);
await openedNotebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => {
editBuilder.insertCell({
cell_type: 'code',
source: cellStatements
}, insertionPosition);
});
}
@@ -59,9 +78,9 @@ export class NotebookService implements INotebookService {
* @param title the title of the notebook
* @param content the notebook content
*/
launchNotebookWithContent(title: string, content: string): Thenable<azdata.nb.NotebookEditor> {
const uri: vscode.Uri = vscode.Uri.parse(`untitled:${title}`);
return azdata.nb.showNotebookDocument(uri, {
async launchNotebookWithContent(title: string, content: string): Promise<azdata.nb.NotebookEditor> {
const uri: vscode.Uri = vscode.Uri.parse(`untitled:${this.findNextUntitledEditorName(title)}`);
return await azdata.nb.showNotebookDocument(uri, {
connectionProfile: undefined,
preview: false,
initialContent: content,
@@ -70,7 +89,7 @@ export class NotebookService implements INotebookService {
}
async getNotebook(notebook: string | NotebookInfo): Promise<Notebook> {
async getNotebook(notebook: string | NotebookPathInfo): Promise<Notebook> {
const notebookPath = await this.getNotebookFullPath(notebook);
return <Notebook>JSON.parse(await this.platformService.readTextFile(notebookPath));
}
@@ -83,7 +102,7 @@ export class NotebookService implements INotebookService {
const outputFullPath = path.join(workingDirectory, `output-${fileName}`);
const additionalEnvironmentVariables: NodeJS.ProcessEnv = env || {};
// Set the azdata eula
// Scenarios using the executeNotebook feature already have the EULA acceptted by the user before executing this.
// Scenarios using the executeNotebook feature already have the EULA accepted by the user before executing this.
additionalEnvironmentVariables['ACCEPT_EULA'] = 'yes';
try {
await this.platformService.saveTextFile(content, notebookFullPath);
@@ -109,15 +128,17 @@ export class NotebookService implements INotebookService {
}
}
public backgroundExecuteNotebook(taskName: string | undefined, notebookInfo: string | NotebookInfo, tempNotebookPrefix: string, platformService: IPlatformService): void {
backgroundExecuteNotebook(taskName: string = 'Executing notebook', notebookInfo: string | NotebookPathInfo | Notebook, tempNotebookPrefix: string, platformService: IPlatformService, env?: NodeJS.ProcessEnv): void {
azdata.tasks.startBackgroundOperation({
displayName: taskName!,
description: taskName!,
displayName: taskName,
description: taskName,
isCancelable: false,
operation: async op => {
op.updateStatus(azdata.TaskStatus.InProgress);
const notebook = await this.getNotebook(notebookInfo);
const result = await this.executeNotebook(notebook);
const notebook = (typeof notebookInfo === 'object' && 'cells' in notebookInfo)
? <Notebook>notebookInfo
: await this.getNotebook(notebookInfo);
const result = await this.executeNotebook(notebook, env);
if (result.succeeded) {
op.updateStatus(azdata.TaskStatus.Succeeded);
} else {
@@ -129,7 +150,7 @@ export class NotebookService implements INotebookService {
platformService.logToOutputChannel(taskFailedMessage);
if (selectedOption === viewErrorDetail) {
try {
this.launchNotebookWithContent(`${tempNotebookPrefix}-${getDateTimeString()}`, result.outputNotebook);
await this.launchNotebookWithContent(`${tempNotebookPrefix}-${getDateTimeString()}`, result.outputNotebook);
} catch (error) {
const launchNotebookError = localize('resourceDeployment.FailedToOpenNotebook', "An error occurred launching the output notebook. {1}{2}.", EOL, getErrorMessage(error));
platformService.logToOutputChannel(launchNotebookError);
@@ -146,7 +167,7 @@ export class NotebookService implements INotebookService {
});
}
async getNotebookFullPath(notebook: string | NotebookInfo): Promise<string> {
async getNotebookFullPath(notebook: string | NotebookPathInfo): Promise<string> {
const notebookPath = this.getNotebookPath(notebook);
let notebookExists = await this.platformService.fileExists(notebookPath);
if (notebookExists) {
@@ -168,7 +189,7 @@ export class NotebookService implements INotebookService {
* get the notebook path for current platform
* @param notebook the notebook path
*/
getNotebookPath(notebook: string | NotebookInfo): string {
getNotebookPath(notebook: string | NotebookPathInfo): string {
let notebookPath;
if (notebook && !isString(notebook)) {
const platform = this.platformService.platform();
@@ -199,17 +220,16 @@ export class NotebookService implements INotebookService {
return title;
}
showNotebookAsUntitled(notebookPath: string): Thenable<azdata.nb.NotebookEditor> {
async showNotebookAsUntitled(notebookPath: string): Promise<azdata.nb.NotebookEditor> {
let targetFileName: string = this.findNextUntitledEditorName(notebookPath);
const untitledFileName: vscode.Uri = vscode.Uri.parse(`untitled:${targetFileName}`);
return vscode.workspace.openTextDocument(notebookPath).then((document) => {
const document = await vscode.workspace.openTextDocument(notebookPath);
let initialContent = document.getText();
return azdata.nb.showNotebookDocument(untitledFileName, {
return await azdata.nb.showNotebookDocument(untitledFileName, {
connectionProfile: undefined,
preview: false,
initialContent: initialContent,
initialDirtyState: false
});
});
}
}

View File

@@ -124,7 +124,8 @@ export class PlatformService implements IPlatformService {
}
isNotebookNameUsed(title: string): boolean {
return (azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1);
return (azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1)
&& (vscode.workspace.textDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1);
}
async makeDirectory(path: string): Promise<void> {

View File

@@ -13,7 +13,7 @@ import * as nls from 'vscode-nls';
import { INotebookService } from './notebookService';
import { IPlatformService } from './platformService';
import { IToolsService } from './toolsService';
import { ResourceType, ResourceTypeOption, NotebookInfo, DeploymentProvider, instanceOfWizardDeploymentProvider, instanceOfDialogDeploymentProvider, instanceOfNotebookDeploymentProvider, instanceOfDownloadDeploymentProvider, instanceOfWebPageDeploymentProvider, instanceOfCommandDeploymentProvider, instanceOfNotebookBasedDialogInfo, instanceOfNotebookWizardDeploymentProvider } from '../interfaces';
import { ResourceType, ResourceTypeOption, NotebookPathInfo, DeploymentProvider, instanceOfWizardDeploymentProvider, instanceOfDialogDeploymentProvider, instanceOfNotebookDeploymentProvider, instanceOfDownloadDeploymentProvider, instanceOfWebPageDeploymentProvider, instanceOfCommandDeploymentProvider, instanceOfNotebookBasedDialogInfo, instanceOfNotebookWizardDeploymentProvider } from '../interfaces';
import { DeployClusterWizard } from '../ui/deployClusterWizard/deployClusterWizard';
import { DeploymentInputDialog } from '../ui/deploymentInputDialog';
@@ -77,7 +77,7 @@ export class ResourceTypeService implements IResourceTypeService {
});
}
private updateNotebookPath(objWithNotebookProperty: { notebook: string | NotebookInfo } | undefined, extensionPath: string): void {
private updateNotebookPath(objWithNotebookProperty: { notebook: string | NotebookPathInfo } | undefined, extensionPath: string): void {
if (objWithNotebookProperty && objWithNotebookProperty.notebook) {
if (typeof objWithNotebookProperty.notebook === 'string') {
objWithNotebookProperty.notebook = path.join(extensionPath, objWithNotebookProperty.notebook);
@@ -239,10 +239,10 @@ export class ResourceTypeService implements IResourceTypeService {
public startDeployment(provider: DeploymentProvider): void {
const self = this;
if (instanceOfWizardDeploymentProvider(provider)) {
const wizard = new DeployClusterWizard(provider.bdcWizard, new KubeService(), new AzdataService(this.platformService), this.notebookService);
const wizard = new DeployClusterWizard(provider.bdcWizard, new KubeService(), new AzdataService(this.platformService), this.notebookService, this.toolsService);
wizard.open();
} else if (instanceOfNotebookWizardDeploymentProvider(provider)) {
const wizard = new NotebookWizard(provider.notebookWizard, this.notebookService, this.platformService);
const wizard = new NotebookWizard(provider.notebookWizard, this.notebookService, this.platformService, this.toolsService);
wizard.open();
} else if (instanceOfDialogDeploymentProvider(provider)) {
const dialog = new DeploymentInputDialog(this.notebookService, this.platformService, provider.dialog);

View File

@@ -11,10 +11,12 @@ import { IPlatformService } from './platformService';
export interface IToolsService {
getToolByName(toolName: string): ITool | undefined;
toolsForCurrentProvider: ITool[];
}
export class ToolsService implements IToolsService {
private supportedTools: Map<string, ITool>;
private currentTools: ITool[] = [];
constructor(private _platformService: IPlatformService) {
this.supportedTools = new Map<string, ITool>(
@@ -30,4 +32,12 @@ export class ToolsService implements IToolsService {
getToolByName(toolName: string): ITool | undefined {
return this.supportedTools.get(toolName);
}
get toolsForCurrentProvider(): ITool[] {
return this.currentTools;
}
set toolsForCurrentProvider(tools: ITool[]) {
this.currentTools = tools;
}
}

View File

@@ -7,7 +7,7 @@ import * as TypeMoq from 'typemoq';
import 'mocha';
import { NotebookService } from '../services/notebookService';
import assert = require('assert');
import { NotebookInfo } from '../interfaces';
import { NotebookPathInfo } from '../interfaces';
import { IPlatformService } from '../services/platformService';
suite('Notebook Service Tests', function (): void {
@@ -35,7 +35,7 @@ suite('Notebook Service Tests', function (): void {
const notebookDarwin = 'test-notebook-darwin.ipynb';
const notebookLinux = 'test-notebook-linux.ipynb';
const notebookInput: NotebookInfo = {
const notebookInput: NotebookPathInfo = {
darwin: notebookDarwin,
win32: notebookWin32,
linux: notebookLinux

View File

@@ -4,28 +4,33 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { SummaryPage } from './pages/summaryPage';
import { WizardBase } from '../wizardBase';
import * as nls from 'vscode-nls';
import { WizardInfo, BdcDeploymentType } from '../../interfaces';
import { WizardPageBase } from '../wizardPageBase';
import { AzureSettingsPage } from './pages/azureSettingsPage';
import { ClusterSettingsPage } from './pages/clusterSettingsPage';
import { ServiceSettingsPage } from './pages/serviceSettingsPage';
import { TargetClusterContextPage } from './pages/targetClusterPage';
import { IKubeService } from '../../services/kubeService';
import { IAzdataService } from '../../services/azdataService';
import { DeploymentProfilePage } from './pages/deploymentProfilePage';
import { INotebookService } from '../../services/notebookService';
import { DeployClusterWizardModel, AuthenticationMode } from './deployClusterWizardModel';
import * as VariableNames from './constants';
import * as fs from 'fs';
import * as os from 'os';
import { join } from 'path';
import * as fs from 'fs';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { BdcDeploymentType, BdcWizardInfo } from '../../interfaces';
import { IAzdataService } from '../../services/azdataService';
import { IKubeService } from '../../services/kubeService';
import { INotebookService } from '../../services/notebookService';
import { IToolsService } from '../../services/toolsService';
import { getErrorMessage } from '../../utils';
import { InputComponents } from '../modelViewUtils';
import { WizardBase } from '../wizardBase';
import { WizardPageBase } from '../wizardPageBase';
import * as VariableNames from './constants';
import { AuthenticationMode, DeployClusterWizardModel } from './deployClusterWizardModel';
import { AzureSettingsPage } from './pages/azureSettingsPage';
import { ClusterSettingsPage } from './pages/clusterSettingsPage';
import { DeploymentProfilePage } from './pages/deploymentProfilePage';
import { ServiceSettingsPage } from './pages/serviceSettingsPage';
import { SummaryPage } from './pages/summaryPage';
import { TargetClusterContextPage } from './pages/targetClusterPage';
const localize = nls.loadMessageBundle();
export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployClusterWizardModel> {
export class DeployClusterWizard extends WizardBase<DeployClusterWizard, WizardPageBase<DeployClusterWizard>, DeployClusterWizardModel> {
private _inputComponents: InputComponents = {};
private _saveConfigButton: azdata.window.Button;
public get kubeService(): IKubeService {
@@ -40,6 +45,10 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
return this._notebookService;
}
public get inputComponents(): InputComponents {
return this._inputComponents;
}
public showCustomButtons(): void {
this._saveConfigButton.hidden = false;
}
@@ -48,7 +57,7 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
this._saveConfigButton.hidden = true;
}
constructor(private wizardInfo: WizardInfo, private _kubeService: IKubeService, private _azdataService: IAzdataService, private _notebookService: INotebookService) {
constructor(private wizardInfo: BdcWizardInfo, private _kubeService: IKubeService, private _azdataService: IAzdataService, private _notebookService: INotebookService, private _toolsService: IToolsService) {
super(DeployClusterWizard.getTitle(wizardInfo.type), new DeployClusterWizardModel(wizardInfo.type));
this._saveConfigButton = azdata.window.createButton(localize('deployCluster.SaveConfigFiles', "Save config files"), 'left');
this._saveConfigButton.hidden = true;
@@ -69,8 +78,8 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
protected onCancel(): void {
}
protected onOk(): void {
this.scriptToNotebook();
protected async onOk(): Promise<void> {
await this.scriptToNotebook();
}
private getPages(): WizardPageBase<DeployClusterWizard>[] {
@@ -135,19 +144,15 @@ export class DeployClusterWizard extends WizardBase<DeployClusterWizard, DeployC
}
}
private scriptToNotebook(): void {
private async scriptToNotebook(): Promise<void> {
this.setEnvironmentVariables(process.env);
this.notebookService.launchNotebook(this.wizardInfo.notebook).then((notebook: azdata.nb.NotebookEditor) => {
notebook.edit((editBuilder: azdata.nb.NotebookEditorEdit) => {
// 5 is the position after the 'Set variables' cell in the deployment notebooks
editBuilder.insertCell({
cell_type: 'code',
source: this.model.getCodeCellContentForNotebook()
}, 5);
});
}, (error) => {
vscode.window.showErrorMessage(error);
});
const variableValueStatements = this.model.getCodeCellContentForNotebook(this._toolsService.toolsForCurrentProvider);
const insertionPosition = 5; // Cell number 5 is the position where the python variable setting statements need to be inserted in this.wizardInfo.notebook.
try {
await this.notebookService.launchNotebookWithEdits(this.wizardInfo.notebook, variableValueStatements, insertionPosition);
} catch (error) {
vscode.window.showErrorMessage(getErrorMessage(error));
}
}
private setEnvironmentVariables(env: NodeJS.ProcessEnv): void {

View File

@@ -4,13 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { EOL } from 'os';
import { delimiter } from 'path';
import { BdcDeploymentType } from '../../interfaces';
import { BdcDeploymentType, ITool } from '../../interfaces';
import { BigDataClusterDeploymentProfile, DataResource, HdfsResource, SqlServerMasterResource } from '../../services/bigDataClusterDeploymentProfile';
import { KubeCtlToolName } from '../../services/tools/kubeCtlTool';
import { getRuntimeBinaryPathEnvironmentVariableName } from '../../utils';
import { getRuntimeBinaryPathEnvironmentVariableName, setEnvironmentVariablesForInstallPaths } from '../../utils';
import { Model } from '../model';
import * as VariableNames from './constants';
import { ToolsInstallPath } from './../../constants';
import * as VariableNames from './constants';
export class DeployClusterWizardModel extends Model {
constructor(public deploymentTarget: BdcDeploymentType) {
@@ -138,7 +138,7 @@ export class DeployClusterWizardModel extends Model {
return targetDeploymentProfile;
}
public getCodeCellContentForNotebook(): string[] {
public getCodeCellContentForNotebook(tools: ITool[]): string[] {
const profile = this.createTargetProfile();
const statements: string[] = [];
if (this.deploymentTarget === BdcDeploymentType.NewAKS) {
@@ -166,16 +166,13 @@ export class DeployClusterWizardModel extends Model {
statements.push(`os.environ["DOCKER_PASSWORD"] = os.environ["${VariableNames.DockerPassword_VariableName}"]`);
}
const kubeCtlEnvVarName: string = getRuntimeBinaryPathEnvironmentVariableName(KubeCtlToolName);
statements.push(`os.environ["${kubeCtlEnvVarName}"] = "${this.escapeForNotebookCodeCell(process.env[kubeCtlEnvVarName]!)}"`);
statements.push(`os.environ["PATH"] = os.environ["PATH"] + "${delimiter}" + "${this.escapeForNotebookCodeCell(process.env[ToolsInstallPath]!)}"`);
const env: NodeJS.ProcessEnv = {};
setEnvironmentVariablesForInstallPaths(tools, env);
statements.push(`os.environ["${kubeCtlEnvVarName}"] = "${this.escapeForNotebookCodeCell(env[kubeCtlEnvVarName]!)}"`);
statements.push(`os.environ["PATH"] = os.environ["PATH"] + "${delimiter}" + "${this.escapeForNotebookCodeCell(env[ToolsInstallPath]!)}"`);
statements.push(`print('Variables have been set successfully.')`);
return statements.map(line => line + EOL);
}
private escapeForNotebookCodeCell(original: string): string {
// Escape the \ character for the code cell string value
return original && original.replace(/\\/g, '\\\\');
}
}
export enum AuthenticationMode {

View File

@@ -7,7 +7,7 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { FieldType, LabelPosition, SectionInfo } from '../../../interfaces';
import { createSection, getDropdownComponent, InputComponents, InputComponent, setModelValues, Validator } from '../../modelViewUtils';
import { createSection, getDropdownComponent, InputComponentInfo, InputComponents, setModelValues, Validator } from '../../modelViewUtils';
import { WizardPageBase } from '../../wizardPageBase';
import { AksName_VariableName, Location_VariableName, ResourceGroup_VariableName, SubscriptionId_VariableName, VMCount_VariableName, VMSize_VariableName } from '../constants';
import { DeployClusterWizard } from '../deployClusterWizard';
@@ -29,7 +29,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
labelPosition: LabelPosition.Left,
spaceBetweenFields: '5px',
rows: [{
fields: [{
items: [{
type: FieldType.Text,
label: localize('deployCluster.SubscriptionField', "Subscription id"),
required: false,
@@ -38,9 +38,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
description: localize('deployCluster.SubscriptionDescription', "The default subscription will be used if you leave this field blank.")
}, {
type: FieldType.ReadonlyText,
label: '',
labelWidth: '0px',
defaultValue: localize('deployCluster.SubscriptionHelpText', "{0}"),
label: '{0}',
links: [
{
text: localize('deployCluster.SubscriptionHelpLink', "View available Azure subscriptions"),
@@ -49,7 +47,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
]
}]
}, {
fields: [{
items: [{
type: FieldType.DateTimeText,
label: localize('deployCluster.ResourceGroupName', "New resource group name"),
required: true,
@@ -57,7 +55,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
defaultValue: 'mssql-'
}]
}, {
fields: [{
items: [{
type: FieldType.Options,
label: localize('deployCluster.Location', "Location"),
required: true,
@@ -79,9 +77,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
]
}, {
type: FieldType.ReadonlyText,
label: '',
labelWidth: '0px',
defaultValue: localize('deployCluster.LocationHelpText', "{0}"),
label: '{0}',
links: [
{
text: localize('deployCluster.AzureLocationHelpLink', "View available Azure locations"),
@@ -90,7 +86,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
]
}]
}, {
fields: [{
items: [{
type: FieldType.DateTimeText,
label: localize('deployCluster.AksName', "AKS cluster name"),
required: true,
@@ -98,7 +94,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
defaultValue: 'mssql-',
}]
}, {
fields: [
items: [
{
type: FieldType.Number,
label: localize('deployCluster.VMCount', "VM count"),
@@ -110,7 +106,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
}
]
}, {
fields: [{
items: [{
type: FieldType.Text,
label: localize('deployCluster.VMSize', "VM size"),
required: true,
@@ -118,9 +114,7 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
defaultValue: 'Standard_E8s_v3'
}, {
type: FieldType.ReadonlyText,
label: '',
labelWidth: '0px',
defaultValue: localize('deployCluster.VMSizeHelpText', "{0}"),
label: '{0}',
links: [
{
text: localize('deployCluster.VMSizeHelpLink', "View available VM sizes"),
@@ -137,13 +131,14 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
self.wizard.registerDisposable(disposable);
},
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
self.inputComponents[name] = { component: component };
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
this.inputComponents[name] = { component: inputComponentInfo.component };
},
onNewValidatorCreated: (validator: Validator): void => {
self.validators.push(validator);
},
container: this.wizard.wizardObject
container: this.wizard.wizardObject,
inputComponents: this.wizard.inputComponents
});
const formBuilder = view.modelBuilder.formContainer().withFormItems(
[{
@@ -184,5 +179,6 @@ export class AzureSettingsPage extends WizardPageBase<DeployClusterWizard> {
return true;
});
setModelValues(this.inputComponents, this.wizard.model);
Object.assign(this.wizard.inputComponents, this.inputComponents);
}
}

View File

@@ -4,14 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { EOL } from 'os';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { DeployClusterWizard } from '../deployClusterWizard';
import { SectionInfo, FieldType, LabelPosition } from '../../../interfaces';
import { createSection, InputComponents, setModelValues, Validator, getInputBoxComponent, isValidSQLPassword, getInvalidSQLPasswordMessage, getPasswordMismatchMessage, InputComponent } from '../../modelViewUtils';
import { FieldType, LabelPosition, SectionInfo } from '../../../interfaces';
import { createSection, getInputBoxComponent, getInvalidSQLPasswordMessage, getPasswordMismatchMessage, InputComponentInfo, InputComponents, isValidSQLPassword, setModelValues, Validator } from '../../modelViewUtils';
import { WizardPageBase } from '../../wizardPageBase';
import * as VariableNames from '../constants';
import { EOL } from 'os';
import { DeployClusterWizard } from '../deployClusterWizard';
import { AuthenticationMode } from '../deployClusterWizardModel';
const localize = nls.loadMessageBundle();
@@ -174,7 +174,7 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
variableName: VariableNames.DomainServiceAccountPassword_VariableName
}, {
type: FieldType.Text,
label: localize('deployCluster.AppOwers', "App owners"),
label: localize('deployCluster.AppOwners', "App owners"),
required: false,
variableName: VariableNames.AppOwners_VariableName,
placeHolder: localize('deployCluster.AppOwnersPlaceHolder', "Use comma to separate the values."),
@@ -193,12 +193,13 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
const basicSettingsGroup = createSection({
view: view,
container: self.wizard.wizardObject,
inputComponents: this.wizard.inputComponents,
sectionInfo: basicSection,
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
self.wizard.registerDisposable(disposable);
},
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
self.inputComponents[name] = { component: component };
onNewInputComponentCreated: (name: string, inputComponent: InputComponentInfo): void => {
self.inputComponents[name] = { component: inputComponent.component };
},
onNewValidatorCreated: (validator: Validator): void => {
self.validators.push(validator);
@@ -207,12 +208,13 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
const activeDirectorySettingsGroup = createSection({
view: view,
container: self.wizard.wizardObject,
inputComponents: this.wizard.inputComponents,
sectionInfo: activeDirectorySection,
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
self.wizard.registerDisposable(disposable);
},
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
self.inputComponents[name] = { component: component };
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
this.inputComponents[name] = { component: inputComponentInfo.component };
},
onNewValidatorCreated: (validator: Validator): void => {
self.validators.push(validator);
@@ -221,12 +223,13 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
const dockerSettingsGroup = createSection({
view: view,
container: self.wizard.wizardObject,
inputComponents: this.wizard.inputComponents,
sectionInfo: dockerSection,
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
self.wizard.registerDisposable(disposable);
},
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
self.inputComponents[name] = { component: component };
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
this.inputComponents[name] = { component: inputComponentInfo.component };
},
onNewValidatorCreated: (validator: Validator): void => {
self.validators.push(validator);
@@ -266,6 +269,7 @@ export class ClusterSettingsPage extends WizardPageBase<DeployClusterWizard> {
public onLeave() {
setModelValues(this.inputComponents, this.wizard.model);
Object.assign(this.wizard.inputComponents, this.inputComponents);
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
const variableDNSPrefixMapping: { [s: string]: string } = {};
variableDNSPrefixMapping[VariableNames.AppServiceProxyDNSName_VariableName] = 'bdc-appproxy';

View File

@@ -5,11 +5,11 @@
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { DeployClusterWizard } from '../deployClusterWizard';
import { SectionInfo, FieldType } from '../../../interfaces';
import { Validator, InputComponents, InputComponent, createSection, createGroupContainer, createLabel, createFlexContainer, createTextInput, createNumberInput, setModelValues, getInputBoxComponent, getCheckboxComponent, getDropdownComponent } from '../../modelViewUtils';
import { FieldType, SectionInfo } from '../../../interfaces';
import { createFlexContainer, createGroupContainer, createLabel, createNumberInput, createSection, createTextInput, getCheckboxComponent, getDropdownComponent, getInputBoxComponent, InputComponentInfo, InputComponents, setModelValues, Validator } from '../../modelViewUtils';
import { WizardPageBase } from '../../wizardPageBase';
import * as VariableNames from '../constants';
import { DeployClusterWizard } from '../deployClusterWizard';
import { AuthenticationMode } from '../deployClusterWizardModel';
const localize = nls.loadMessageBundle();
@@ -59,7 +59,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
inputWidth: NumberInputWidth,
spaceBetweenFields: '40px',
rows: [{
fields: [{
items: [{
type: FieldType.Options,
label: localize('deployCluster.MasterSqlServerInstances', "SQL Server master instances"),
options: ['1', '3', '4', '5', '6', '7', '8', '9'],
@@ -75,7 +75,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
variableName: VariableNames.ComputePoolScale_VariableName,
}]
}, {
fields: [{
items: [{
type: FieldType.Number,
label: localize('deployCluster.DataPoolInstances', "Data pool instances"),
min: 1,
@@ -93,7 +93,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
variableName: VariableNames.SparkPoolScale_VariableName
}]
}, {
fields: [
items: [
{
type: FieldType.Number,
label: localize('deployCluster.StoragePoolInstances', "Storage pool (HDFS) instances"),
@@ -119,12 +119,13 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
return createSection({
view: view,
container: this.wizard.wizardObject,
inputComponents: this.inputComponents,
sectionInfo: sectionInfo,
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
this.wizard.registerDisposable(disposable);
},
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
this.inputComponents[name] = { component: component };
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
this.inputComponents[name] = { component: inputComponentInfo.component };
},
onNewValidatorCreated: (validator: Validator): void => {
}
@@ -400,6 +401,7 @@ export class ServiceSettingsPage extends WizardPageBase<DeployClusterWizard> {
public onLeave(): void {
setModelValues(this.inputComponents, this.wizard.model);
Object.assign(this.wizard.inputComponents, this.inputComponents);
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});

View File

@@ -43,18 +43,17 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
title: localize('deployCluster.DeploymentTarget', "Deployment target"),
rows: [
{
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.Kubeconfig', "Kube config"),
defaultValue: this.wizard.model.getStringValue(VariableNames.KubeConfigPath_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterContext', "Cluster context"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterContext_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
}
]
@@ -68,33 +67,33 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
rows: [
{
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DeploymentProfile', "Deployment profile"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DeploymentProfile_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterName', "Cluster name"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterName_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
}, {
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ControllerUsername', "Controller username"),
defaultValue: this.wizard.model.getStringValue(VariableNames.AdminUserName_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.AuthenticationMode', "Authentication mode"),
defaultValue: this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory ?
localize('deployCluster.AuthenticationMode.ActiveDirectory', "Active Directory") :
localize('deployCluster.AuthenticationMode.Basic', "Basic"),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
}
@@ -103,72 +102,72 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
clusterSectionInfo.rows!.push({
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.OuDistinguishedName', "Organizational unit"),
defaultValue: this.wizard.model.getStringValue(VariableNames.OrganizationalUnitDistinguishedName_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DomainControllerFQDNs', "Domain controller FQDNs"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainControllerFQDNs_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
});
clusterSectionInfo.rows!.push({
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DomainDNSIPAddresses', "Domain DNS IP addresses"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainDNSIPAddresses_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DomainDNSName', "Domain DNS name"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainDNSName_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
});
clusterSectionInfo.rows!.push({
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterAdmins', "Cluster admin group"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterAdmins_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.ClusterUsers', "Cluster users"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ClusterUsers_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
});
clusterSectionInfo.rows!.push({
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.AppOwers', "App owners"),
label: localize('deployCluster.AppOwners', "App owners"),
defaultValue: this.wizard.model.getStringValue(VariableNames.AppOwners_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
},
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.AppReaders', "App readers"),
defaultValue: this.wizard.model.getStringValue(VariableNames.AppReaders_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
});
clusterSectionInfo.rows!.push({
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DomainServiceAccountUserName', "Service account username"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DomainServiceAccountUserName_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
});
}
@@ -179,45 +178,45 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
inputWidth: '200px',
title: localize('deployCluster.AzureSettings', "Azure settings"),
rows: [{
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.SubscriptionId', "Subscription id"),
defaultValue: this.wizard.model.getStringValue(VariableNames.SubscriptionId_VariableName) || localize('deployCluster.DefaultSubscription', "Default Azure Subscription"),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.ResourceGroup', "Resource group"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ResourceGroup_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
}, {
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.Location', "Location"),
defaultValue: this.wizard.model.getStringValue(VariableNames.Location_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.AksClusterName', "AKS cluster name"),
defaultValue: this.wizard.model.getStringValue(VariableNames.AksName_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
}, {
fields: [
items: [
{
type: FieldType.ReadonlyText,
label: localize('deployCluster.VMSize', "VM size"),
defaultValue: this.wizard.model.getStringValue(VariableNames.VMSize_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.VMCount', "VM count"),
defaultValue: this.wizard.model.getStringValue(VariableNames.VMCount_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}
]
}
@@ -231,35 +230,35 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
title: localize('deployCluster.ScaleSettings', "Scale settings"),
rows: [
{
fields: [{
items: [{
type: FieldType.ReadonlyText,
label: localize('deployCluster.MasterSqlServerInstances', "SQL Server master instances"),
defaultValue: this.wizard.model.getStringValue(VariableNames.SQLServerScale_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.ComputePoolInstances', "Compute pool instances"),
defaultValue: this.wizard.model.getStringValue(VariableNames.ComputePoolScale_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
}, {
fields: [{
items: [{
type: FieldType.ReadonlyText,
label: localize('deployCluster.DataPoolInstances', "Data pool instances"),
defaultValue: this.wizard.model.getStringValue(VariableNames.DataPoolScale_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}, {
type: FieldType.ReadonlyText,
label: localize('deployCluster.SparkPoolInstances', "Spark pool instances"),
defaultValue: this.wizard.model.getStringValue(VariableNames.SparkPoolScale_VariableName),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
}, {
fields: [{
items: [{
type: FieldType.ReadonlyText,
label: localize('deployCluster.StoragePoolInstances', "Storage pool (HDFS) instances"),
defaultValue: `${this.wizard.model.getStringValue(VariableNames.HDFSPoolScale_VariableName)} ${this.wizard.model.getBooleanValue(VariableNames.IncludeSpark_VariableName) ? localize('deployCluster.WithSpark', "(Spark included)") : ''}`,
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
}
]
@@ -270,6 +269,7 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
title: '',
component: createSection({
container: this.wizard.wizardObject,
inputComponents: this.wizard.inputComponents,
sectionInfo: sectionInfo,
view: this.view,
onNewDisposableCreated: () => { },
@@ -398,7 +398,7 @@ export class SummaryPage extends WizardPageBase<DeployClusterWizard> {
private createEndpointRow(name: string, dnsVariableName: string, portVariableName: string): azdata.FlexContainer {
const items = [];
items.push(createLabel(this.view, { text: name, width: '150px', fontWeight: FontWeight.Bold }));
items.push(createLabel(this.view, { text: name, width: '150px', cssStyles: { fontWeight: FontWeight.Bold } }));
if (this.wizard.model.authenticationMode === AuthenticationMode.ActiveDirectory) {
items.push(createLabel(this.view, {
text: this.wizard.model.getStringValue(dnsVariableName)!, width: '200px'

View File

@@ -4,15 +4,15 @@
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import { EOL } from 'os';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { DialogBase } from './dialogBase';
import { INotebookService } from '../services/notebookService';
import { DialogInfo, instanceOfNotebookBasedDialogInfo, NotebookBasedDialogInfo } from '../interfaces';
import { Validator, initializeDialog, InputComponents, setModelValues, InputValueTransformer, InputComponent } from './modelViewUtils';
import { Model } from './model';
import { EOL } from 'os';
import { INotebookService } from '../services/notebookService';
import { IPlatformService } from '../services/platformService';
import { DialogBase } from './dialogBase';
import { Model } from './model';
import { initializeDialog, InputComponentInfo, InputComponents, setModelValues, Validator } from './modelViewUtils';
const localize = nls.loadMessageBundle();
@@ -42,11 +42,12 @@ export class DeploymentInputDialog extends DialogBase {
initializeDialog({
dialogInfo: this.dialogInfo,
container: this._dialogObject,
inputComponents: this.inputComponents,
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
this._toDispose.push(disposable);
},
onNewInputComponentCreated: (name: string, component: InputComponent, inputValueTransformer?: InputValueTransformer): void => {
this.inputComponents[name] = { component: component, inputValueTransformer: inputValueTransformer };
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo): void => {
this.inputComponents[name] = inputComponentInfo;
},
onNewValidatorCreated: (validator: Validator): void => {
validators.push(validator);

View File

@@ -2,7 +2,14 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { NoteBookEnvironmentVariablePrefix } from '../interfaces';
import { EOL } from 'os';
import { ITool, NoteBookEnvironmentVariablePrefix } from '../interfaces';
import { setEnvironmentVariablesForInstallPaths, getRuntimeBinaryPathEnvironmentVariableName } from '../utils';
import { ToolsInstallPath } from '../constants';
import { delimiter } from 'path';
const NotebookEnvironmentVariablePrefixRegex = new RegExp(`^${NoteBookEnvironmentVariablePrefix}`);
export class Model {
private propValueObject: { [s: string]: string | undefined } = {};
@@ -32,13 +39,57 @@ export class Model {
return value === undefined ? defaultValue : value === 'true';
}
public setEnvironmentVariables(): void {
Object.keys(this.propValueObject).filter(propertyName => propertyName.startsWith(NoteBookEnvironmentVariablePrefix)).forEach(propertyName => {
const value = this.getStringValue(propertyName);
if (value !== undefined && value !== '') {
process.env[propertyName] = value;
/**
* Returns python code statements for setting variables starting with {@see NoteBookEnvironmentVariablePrefix} as python variables.
* The prefix {@see NoteBookEnvironmentVariablePrefix} is removed and variable name changed to all lowercase to arrive at python variable name.
* The statements returned are escaped for use in cell of a python notebook.
*
* @param tools - optional set of tools for which variable value setting statements need to be generated;
* @param inputFilter - optional parameter to filter out setting of specific variable names. Every variable for which this function returns false is not included
* in the emitted code.
*/
public getCodeCellContentForNotebook(tools: ITool[] = [], inputFilter: (varName: string) => boolean = () => true): string[] {
const statements: string[] = Object.keys(this.propValueObject)
.filter(propertyName => propertyName.startsWith(NoteBookEnvironmentVariablePrefix) && inputFilter(propertyName))
.map(propertyName => {
const value = this.escapeForNotebookCodeCell(this.getStringValue(propertyName, ''));
const varName = propertyName.replace(NotebookEnvironmentVariablePrefixRegex, '').toLocaleLowerCase();
return `${varName} = '${value}'${EOL}`;
});
statements.push(`print('Variables have been set successfully.')${EOL}`);
const env: NodeJS.ProcessEnv = {};
setEnvironmentVariablesForInstallPaths(tools, env);
tools.forEach(tool => {
const envVarName: string = getRuntimeBinaryPathEnvironmentVariableName(tool.name);
statements.push(`os.environ["${envVarName}"] = "${this.escapeForNotebookCodeCell(env[envVarName]!)}"${EOL}`);
});
if (env[ToolsInstallPath]) {
statements.push(`os.environ["PATH"] = os.environ["PATH"] + "${delimiter}" + "${this.escapeForNotebookCodeCell(env[ToolsInstallPath])}"${EOL}`);
}
process.env[propertyName] = value === undefined ? '' : value;
statements.push(`print('Environment Variables for tools have been set successfully.')${EOL}`);
return statements;
}
protected escapeForNotebookCodeCell(original?: string): string | undefined {
// Escape the \ character for the code cell string value
return original && original.replace(/\\/g, '\\\\');
}
/**
* Sets the environment variable for each model variable that starts with {@see NoteBookEnvironmentVariablePrefix} in the
* current process.
*
* @param env - env variable object in which the environment variables are populated. Default: process.env
* @param inputFilter - an optional filter to further restrict the variables that are set into the env object.
* Every variable for which this function returns false is not included does not get the env variable set.
* Default all variable meeting prefix requirements are set.
*/
public setEnvironmentVariables(env: NodeJS.ProcessEnv = process.env, inputFilter: (varName: string) => boolean = () => true): void {
Object.keys(this.propValueObject)
.filter(propertyName => propertyName.startsWith(NoteBookEnvironmentVariablePrefix) && inputFilter(propertyName))
.forEach(propertyName => {
const value = this.getStringValue(propertyName);
env[propertyName] = value === undefined ? '' : value;
});
}
}

View File

@@ -9,19 +9,28 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as azurecore from '../../../azurecore/src/azurecore';
import { azureResource } from '../../../azurecore/src/azureResource/azure-resource';
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, DialogInfoBase, FieldInfo, FieldType, FontStyle, FontWeight, LabelPosition, PageInfoBase, SectionInfo, KubeClusterContextFieldInfo } from '../interfaces';
import { AzureAccountFieldInfo, AzureLocationsFieldInfo, ComponentCSSStyles, DialogInfoBase, FieldInfo, FieldType, KubeClusterContextFieldInfo, LabelPosition, NoteBookEnvironmentVariablePrefix, OptionsInfo, OptionsType, PageInfoBase, RowInfo, SectionInfo, TextCSSStyles } from '../interfaces';
import * as loc from '../localizedConstants';
import { getDefaultKubeConfigPath, getKubeConfigClusterContexts } from '../services/kubeService';
import { getDateTimeString, getErrorMessage } from '../utils';
import { assert, getDateTimeString, getErrorMessage } from '../utils';
import { WizardInfoBase } from './../interfaces';
import { Model } from './model';
import { RadioGroupLoadingComponentBuilder } from './radioGroupLoadingComponentBuilder';
const localize = nls.loadMessageBundle();
export type Validator = () => { valid: boolean, message: string };
export type InputValueTransformer = (inputValue: string) => string;
export type InputComponent = azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | azdata.RadioButtonComponent;
export type InputComponents = { [s: string]: { component: InputComponent; inputValueTransformer?: InputValueTransformer } };
export type InputComponent = azdata.TextComponent | azdata.InputBoxComponent | azdata.DropDownComponent | azdata.CheckBoxComponent | RadioGroupLoadingComponentBuilder;
export type InputComponentInfo = {
component: InputComponent;
inputValueTransformer?: InputValueTransformer;
isPassword?: boolean
};
export type InputComponents = {
[s: string]: InputComponentInfo
};
export function getInputBoxComponent(name: string, inputComponents: InputComponents): azdata.InputBoxComponent {
return <azdata.InputBoxComponent>inputComponents[name].component;
@@ -39,40 +48,42 @@ export function getTextComponent(name: string, inputComponents: InputComponents)
return <azdata.TextComponent>inputComponents[name].component;
}
export const DefaultInputComponentWidth = '400px';
export const DefaultLabelComponentWidth = '200px';
export const DefaultInputWidth = '400px';
export const DefaultLabelWidth = '200px';
export const DefaultFieldAlignItems = undefined;
export const DefaultFieldWidth = undefined;
export const DefaultFieldHeight = undefined;
export interface DialogContext extends CreateContext {
export interface DialogContext extends ContextBase {
dialogInfo: DialogInfoBase;
container: azdata.window.Dialog;
}
export interface WizardPageContext extends CreateContext {
export interface WizardPageContext extends ContextBase {
wizardInfo: WizardInfoBase;
pageInfo: PageInfoBase;
page: azdata.window.WizardPage;
container: azdata.window.Wizard;
}
export interface SectionContext extends CreateContext {
export interface SectionContext extends ContextBase {
sectionInfo: SectionInfo;
view: azdata.ModelView;
}
interface FieldContext extends CreateContext {
export interface FieldContext extends ContextBase {
fieldInfo: FieldInfo;
components: azdata.Component[];
view: azdata.ModelView;
}
interface FilePickerInputs {
export interface FilePickerInputs {
input: azdata.InputBoxComponent;
browseButton: azdata.ButtonComponent;
}
interface RadioOptionsInputs {
optionsList: azdata.DivContainer;
loader: azdata.LoadingComponent;
interface ReadOnlyFieldInputs {
label: azdata.TextComponent;
text?: azdata.TextComponent;
}
interface KubeClusterContextFieldContext extends FieldContext {
@@ -87,11 +98,12 @@ interface AzureAccountFieldContext extends FieldContext {
fieldInfo: AzureAccountFieldInfo;
}
interface CreateContext {
interface ContextBase {
container: azdata.window.Dialog | azdata.window.Wizard;
inputComponents: InputComponents;
onNewValidatorCreated: (validator: Validator) => void;
onNewDisposableCreated: (disposable: vscode.Disposable) => void;
onNewInputComponentCreated: (name: string, component: InputComponent, inputValueTransformer?: InputValueTransformer) => void;
onNewInputComponentCreated: (name: string, inputComponentInfo: InputComponentInfo) => void;
}
export function createTextInput(view: azdata.ModelView, inputInfo: { defaultValue?: string, ariaLabel: string, required?: boolean, placeHolder?: string, width?: string, enabled?: boolean }): azdata.InputBoxComponent {
@@ -106,12 +118,20 @@ export function createTextInput(view: azdata.ModelView, inputInfo: { defaultValu
}).component();
}
export function createLabel(view: azdata.ModelView, info: { text: string, description?: string, required?: boolean, width?: string, fontStyle?: FontStyle, fontWeight?: FontWeight, links?: azdata.LinkArea[] }): azdata.TextComponent {
export function createLabel(view: azdata.ModelView, info: { text: string, description?: string, required?: boolean, width?: string, links?: azdata.LinkArea[], cssStyles?: TextCSSStyles }): azdata.TextComponent {
let cssStyles: { [key: string]: string } = {};
if (info.cssStyles !== undefined) {
cssStyles = Object.assign(cssStyles, info.cssStyles, { 'font-style': info.cssStyles.fontStyle || 'normal', 'font-weight': info.cssStyles.fontWeight || 'normal' });
if (info.cssStyles.color !== undefined) {
cssStyles['color'] = info.cssStyles.color;
}
}
const text = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
value: info.text,
description: info.description,
requiredIndicator: info.required,
CSSStyles: { 'font-style': info.fontStyle || 'normal', 'font-weight': info.fontWeight || 'normal' },
CSSStyles: cssStyles,
links: info.links
}).component();
text.width = info.width;
@@ -157,8 +177,11 @@ export function initializeDialog(dialogContext: DialogContext): void {
const tab = azdata.window.createTab(tabInfo.title);
tab.registerContent((view: azdata.ModelView) => {
const sections = tabInfo.sections.map(sectionInfo => {
sectionInfo.inputWidth = sectionInfo.inputWidth || tabInfo.inputWidth || DefaultInputComponentWidth;
sectionInfo.labelWidth = sectionInfo.labelWidth || tabInfo.labelWidth || DefaultLabelComponentWidth;
sectionInfo.inputWidth = sectionInfo.inputWidth || tabInfo.inputWidth || DefaultInputWidth;
sectionInfo.labelWidth = sectionInfo.labelWidth || tabInfo.labelWidth || DefaultLabelWidth;
sectionInfo.fieldAlignItems = sectionInfo.fieldAlignItems || tabInfo.fieldAlignItems || DefaultFieldAlignItems;
sectionInfo.fieldWidth = sectionInfo.fieldWidth || tabInfo.fieldWidth || DefaultFieldWidth;
sectionInfo.fieldHeight = sectionInfo.fieldHeight || tabInfo.fieldHeight || DefaultFieldHeight;
sectionInfo.labelPosition = sectionInfo.labelPosition || tabInfo.labelPosition;
return createSection({
sectionInfo: sectionInfo,
@@ -166,7 +189,8 @@ export function initializeDialog(dialogContext: DialogContext): void {
onNewDisposableCreated: dialogContext.onNewDisposableCreated,
onNewInputComponentCreated: dialogContext.onNewInputComponentCreated,
onNewValidatorCreated: dialogContext.onNewValidatorCreated,
container: dialogContext.container
container: dialogContext.container,
inputComponents: dialogContext.inputComponents
});
});
const formBuilder = view.modelBuilder.formContainer().withFormItems(
@@ -189,12 +213,16 @@ export function initializeDialog(dialogContext: DialogContext): void {
export function initializeWizardPage(context: WizardPageContext): void {
context.page.registerContent((view: azdata.ModelView) => {
const sections = context.pageInfo.sections.map(sectionInfo => {
sectionInfo.inputWidth = sectionInfo.inputWidth || context.pageInfo.inputWidth || context.wizardInfo.inputWidth || DefaultInputComponentWidth;
sectionInfo.labelWidth = sectionInfo.labelWidth || context.pageInfo.labelWidth || context.wizardInfo.labelWidth || DefaultLabelComponentWidth;
sectionInfo.inputWidth = sectionInfo.inputWidth || context.pageInfo.inputWidth || context.wizardInfo.inputWidth || DefaultInputWidth;
sectionInfo.labelWidth = sectionInfo.labelWidth || context.pageInfo.labelWidth || context.wizardInfo.labelWidth || DefaultLabelWidth;
sectionInfo.fieldAlignItems = sectionInfo.fieldAlignItems || context.pageInfo.fieldAlignItems || DefaultFieldAlignItems;
sectionInfo.fieldWidth = sectionInfo.fieldWidth || context.pageInfo.fieldWidth || context.wizardInfo.fieldWidth || DefaultFieldWidth;
sectionInfo.fieldHeight = sectionInfo.fieldHeight || context.pageInfo.fieldHeight || context.wizardInfo.fieldHeight || DefaultFieldHeight;
sectionInfo.labelPosition = sectionInfo.labelPosition || context.pageInfo.labelPosition || context.wizardInfo.labelPosition;
return createSection({
view: view,
container: context.container,
inputComponents: context.inputComponents,
onNewDisposableCreated: context.onNewDisposableCreated,
onNewInputComponentCreated: context.onNewInputComponentCreated,
onNewValidatorCreated: context.onNewValidatorCreated,
@@ -215,16 +243,16 @@ export function initializeWizardPage(context: WizardPageContext): void {
export function createSection(context: SectionContext): azdata.GroupContainer {
const components: azdata.Component[] = [];
context.sectionInfo.inputWidth = context.sectionInfo.inputWidth || DefaultInputComponentWidth;
context.sectionInfo.labelWidth = context.sectionInfo.labelWidth || DefaultLabelComponentWidth;
context.sectionInfo.inputWidth = context.sectionInfo.inputWidth || DefaultInputWidth;
context.sectionInfo.labelWidth = context.sectionInfo.labelWidth || DefaultLabelWidth;
context.sectionInfo.fieldAlignItems = context.sectionInfo.fieldAlignItems || DefaultFieldAlignItems;
context.sectionInfo.fieldWidth = context.sectionInfo.fieldWidth || DefaultFieldWidth;
context.sectionInfo.fieldHeight = context.sectionInfo.fieldHeight || DefaultFieldHeight;
if (context.sectionInfo.fields) {
processFields(context.sectionInfo.fields, components, context);
} else if (context.sectionInfo.rows) {
context.sectionInfo.rows.forEach(rowInfo => {
const rowItems: azdata.Component[] = [];
processFields(rowInfo.fields, rowItems, context, context.sectionInfo.spaceBetweenFields || '50px');
const row = createFlexContainer(context.view, rowItems);
components.push(row);
components.push(processRow(rowInfo, context));
});
}
@@ -235,11 +263,26 @@ export function createSection(context: SectionContext): azdata.GroupContainer {
});
}
function processRow(rowInfo: RowInfo, context: SectionContext): azdata.Component {
const items: azdata.Component[] = [];
if ('items' in rowInfo.items[0]) { // rowInfo.items is RowInfo[]
const rowItems = rowInfo.items as RowInfo[];
items.push(...rowItems.map(rowInfo => processRow(rowInfo, context)));
} else { // rowInfo.items is FieldInfo[]
const fieldItems = rowInfo.items as FieldInfo[];
processFields(fieldItems, items, context, context.sectionInfo.spaceBetweenFields === undefined ? '50px' : context.sectionInfo.spaceBetweenFields);
}
return createFlexContainer(context.view, items, true, context.sectionInfo.fieldWidth, context.sectionInfo.fieldHeight, context.sectionInfo.fieldAlignItems, rowInfo.cssStyles);
}
function processFields(fieldInfoArray: FieldInfo[], components: azdata.Component[], context: SectionContext, spaceBetweenFields?: string): void {
for (let i = 0; i < fieldInfoArray.length; i++) {
const fieldInfo = fieldInfoArray[i];
fieldInfo.labelWidth = fieldInfo.labelWidth || context.sectionInfo.labelWidth;
fieldInfo.inputWidth = fieldInfo.inputWidth || context.sectionInfo.inputWidth;
fieldInfo.fieldAlignItems = fieldInfo.fieldAlignItems || context.sectionInfo.fieldAlignItems;
fieldInfo.fieldWidth = fieldInfo.fieldWidth || context.sectionInfo.fieldWidth;
fieldInfo.fieldHeight = fieldInfo.fieldHeight || context.sectionInfo.fieldHeight;
fieldInfo.labelPosition = fieldInfo.labelPosition === undefined ? context.sectionInfo.labelPosition : fieldInfo.labelPosition;
processField({
view: context.view,
@@ -248,6 +291,7 @@ function processFields(fieldInfoArray: FieldInfo[], components: azdata.Component
onNewValidatorCreated: context.onNewValidatorCreated,
fieldInfo: fieldInfo,
container: context.container,
inputComponents: context.inputComponents,
components: components
});
if (spaceBetweenFields && i < fieldInfoArray.length - 1) {
@@ -256,24 +300,37 @@ function processFields(fieldInfoArray: FieldInfo[], components: azdata.Component
}
}
export function createFlexContainer(view: azdata.ModelView, items: azdata.Component[], rowLayout: boolean = true): azdata.FlexContainer {
export function createFlexContainer(view: azdata.ModelView, items: azdata.Component[], rowLayout: boolean = true, width?: string | number, height?: string | number, alignItems?: azdata.AlignItemsType, cssStyles?: ComponentCSSStyles): azdata.FlexContainer {
const flexFlow = rowLayout ? 'row' : 'column';
const alignItems = rowLayout ? 'center' : undefined;
const itemsStyle = rowLayout ? { CSSStyles: { 'margin-right': '5px' } } : {};
return view.modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout({ flexFlow: flexFlow, alignItems: alignItems }).component();
alignItems = alignItems || (rowLayout ? 'center' : undefined);
const itemsStyle = rowLayout ? { CSSStyles: { 'margin-right': '5px', } } : {};
const flexLayout: azdata.FlexLayout = { flexFlow: flexFlow };
if (height) {
flexLayout.height = height;
}
if (width) {
flexLayout.width = width;
}
if (alignItems) {
flexLayout.alignItems = alignItems;
}
return view.modelBuilder.flexContainer().withItems(items, itemsStyle).withLayout(flexLayout).withProperties<azdata.ComponentProperties>({ CSSStyles: cssStyles || {} }).component();
}
export function createGroupContainer(view: azdata.ModelView, items: azdata.Component[], layout: azdata.GroupLayout): azdata.GroupContainer {
return view.modelBuilder.groupContainer().withItems(items).withLayout(layout).component();
}
function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata.Component[], label: azdata.Component, input: azdata.Component, labelPosition?: LabelPosition, additionalComponents?: azdata.Component[]) {
const inputs = [label, input];
function addLabelInputPairToContainer(view: azdata.ModelView, components: azdata.Component[], label: azdata.Component, input: azdata.Component | undefined, fieldInfo: FieldInfo, additionalComponents?: azdata.Component[]) {
const inputs: azdata.Component[] = [label];
if (input !== undefined) {
inputs.push(input);
}
if (additionalComponents && additionalComponents.length > 0) {
inputs.push(...additionalComponents);
}
if (labelPosition && labelPosition === LabelPosition.Left) {
const row = createFlexContainer(view, inputs);
if (fieldInfo.labelPosition === LabelPosition.Left) {
const row = createFlexContainer(view, inputs, true, fieldInfo.fieldWidth, fieldInfo.fieldHeight, fieldInfo.fieldAlignItems);
components.push(row);
} else {
components.push(...inputs);
@@ -285,9 +342,6 @@ function processField(context: FieldContext): void {
case FieldType.Options:
processOptionsTypeField(context);
break;
case FieldType.RadioOptions:
processRadioOptionsTypeField(context);
break;
case FieldType.DateTimeText:
processDateTimeTextField(context);
break;
@@ -325,21 +379,42 @@ function processField(context: FieldContext): void {
}
function processOptionsTypeField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const dropdown = createDropdown(context.view, {
assert(context.fieldInfo.options !== undefined, `FieldInfo.options must be defined for FieldType:${FieldType.Options}`);
if (Array.isArray(context.fieldInfo.options)) {
context.fieldInfo.options = <OptionsInfo>{
values: context.fieldInfo.options,
defaultValue: context.fieldInfo.defaultValue,
optionsType: OptionsType.Dropdown
};
}
assert(typeof context.fieldInfo.options === 'object', `FieldInfo.options must be an object if it is not an array`);
assert('optionsType' in context.fieldInfo.options, `When FieldInfo.options is an object it must have 'optionsType' property`);
if (context.fieldInfo.options.optionsType === OptionsType.Radio) {
processRadioOptionsTypeField(context);
} else {
assert(context.fieldInfo.options.optionsType === OptionsType.Dropdown, `When optionsType is not ${OptionsType.Radio} then it must be ${OptionsType.Dropdown}`);
processDropdownOptionsTypeField(context);
}
}
function processDropdownOptionsTypeField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const options = context.fieldInfo.options as OptionsInfo;
const dropdown = createDropdown(context.view, {
values: options.values,
defaultValue: options.defaultValue,
width: context.fieldInfo.inputWidth,
editable: context.fieldInfo.editable,
required: context.fieldInfo.required,
label: context.fieldInfo.label
});
context.onNewInputComponentCreated(context.fieldInfo.variableName!, dropdown);
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo.labelPosition);
dropdown.fireOnTextChange = true;
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: dropdown });
addLabelInputPairToContainer(context.view, context.components, label, dropdown, context.fieldInfo);
}
function processDateTimeTextField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const defaultValue = context.fieldInfo.defaultValue + getDateTimeString();
const input = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
value: defaultValue,
@@ -349,12 +424,12 @@ function processDateTimeTextField(context: FieldContext): void {
placeHolder: context.fieldInfo.placeHolder
}).component();
input.width = context.fieldInfo.inputWidth;
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo.labelPosition);
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo);
}
function processNumberField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const input = createNumberInput(context.view, {
defaultValue: context.fieldInfo.defaultValue,
ariaLabel: context.fieldInfo.label,
@@ -364,12 +439,12 @@ function processNumberField(context: FieldContext): void {
width: context.fieldInfo.inputWidth,
placeHolder: context.fieldInfo.placeHolder
});
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo.labelPosition);
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo);
}
function processTextField(context: FieldContext): void {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const input = createTextInput(context.view, {
defaultValue: context.fieldInfo.defaultValue,
ariaLabel: context.fieldInfo.label,
@@ -378,8 +453,8 @@ function processTextField(context: FieldContext): void {
width: context.fieldInfo.inputWidth,
enabled: context.fieldInfo.enabled
});
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo.labelPosition);
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo);
if (context.fieldInfo.textValidationRequired) {
let validationRegex: RegExp = new RegExp(context.fieldInfo.textValidationRegex!);
@@ -399,12 +474,11 @@ function processTextField(context: FieldContext): void {
return { valid: inputIsValid, message: context.fieldInfo.textValidationDescription! };
};
context.onNewValidatorCreated(inputValidator);
}
}
function processPasswordField(context: FieldContext): void {
const passwordLabel = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const passwordLabel = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const passwordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
ariaLabel: context.fieldInfo.label,
inputType: 'password',
@@ -412,8 +486,8 @@ function processPasswordField(context: FieldContext): void {
placeHolder: context.fieldInfo.placeHolder,
width: context.fieldInfo.inputWidth
}).component();
context.onNewInputComponentCreated(context.fieldInfo.variableName!, passwordInput);
addLabelInputPairToContainer(context.view, context.components, passwordLabel, passwordInput, context.fieldInfo.labelPosition);
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: passwordInput, isPassword: true });
addLabelInputPairToContainer(context.view, context.components, passwordLabel, passwordInput, context.fieldInfo);
if (context.fieldInfo.type === FieldType.SQLPassword) {
const invalidPasswordMessage = getInvalidSQLPasswordMessage(context.fieldInfo.label);
@@ -430,7 +504,7 @@ function processPasswordField(context: FieldContext): void {
if (context.fieldInfo.confirmationRequired) {
const passwordNotMatchMessage = getPasswordMismatchMessage(context.fieldInfo.label);
const confirmPasswordLabel = createLabel(context.view, { text: context.fieldInfo.confirmationLabel!, required: true, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const confirmPasswordLabel = createLabel(context.view, { text: context.fieldInfo.confirmationLabel!, required: true, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const confirmPasswordInput = context.view.modelBuilder.inputBox().withProperties<azdata.InputBoxProperties>({
ariaLabel: context.fieldInfo.confirmationLabel,
inputType: 'password',
@@ -438,7 +512,7 @@ function processPasswordField(context: FieldContext): void {
width: context.fieldInfo.inputWidth
}).component();
addLabelInputPairToContainer(context.view, context.components, confirmPasswordLabel, confirmPasswordInput, context.fieldInfo.labelPosition);
addLabelInputPairToContainer(context.view, context.components, confirmPasswordLabel, confirmPasswordInput, context.fieldInfo);
context.onNewValidatorCreated((): { valid: boolean, message: string } => {
const passwordMatches = passwordInput.value === confirmPasswordInput.value;
return { valid: passwordMatches, message: passwordNotMatchMessage };
@@ -459,34 +533,85 @@ function processPasswordField(context: FieldContext): void {
}
}
function processReadonlyTextField(context: FieldContext): void {
let defaultValue = context.fieldInfo.defaultValue || '';
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const text = createLabel(context.view, { text: defaultValue, description: '', required: false, width: context.fieldInfo.inputWidth, fontWeight: context.fieldInfo.textFontWeight, fontStyle: context.fieldInfo.fontStyle, links: context.fieldInfo.links });
addLabelInputPairToContainer(context.view, context.components, label, text, context.fieldInfo.labelPosition);
function processReadonlyTextField(context: FieldContext, allowEvaluation: boolean = true): ReadOnlyFieldInputs {
if ((context.fieldInfo.links?.length ?? 0) > 0) {
return processHyperlinkedTextField(context);
} else if (context.fieldInfo.isEvaluated && allowEvaluation) {
return processEvaluatedTextField(context);
}
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const text = context.fieldInfo.defaultValue !== undefined
? createLabel(context.view, { text: context.fieldInfo.defaultValue, description: '', required: false, width: context.fieldInfo.inputWidth })
: undefined;
addLabelInputPairToContainer(context.view, context.components, label, text, context.fieldInfo);
return { label: label, text: text };
}
/**
* creates a text component that has text that contains hyperlinks. The context.fieldInfo.label contains {0},{1} ...
* placeholder(s) where contents of link array object are placed with that portion interpolated as a clickable link.
*
* @param context - the FieldContext object using which the field gets created
*/
function processHyperlinkedTextField(context: FieldContext): ReadOnlyFieldInputs {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: false, width: context.fieldInfo.labelWidth, links: context.fieldInfo.links, cssStyles: context.fieldInfo.labelCSSStyles });
context.components.push(label);
return { label: label };
}
function processEvaluatedTextField(context: FieldContext): ReadOnlyFieldInputs {
const readOnlyField = processReadonlyTextField(context, false /*allowEvaluation*/);
context.onNewInputComponentCreated(context.fieldInfo.variableName || context.fieldInfo.label, {
component: readOnlyField.text!,
inputValueTransformer: () => {
readOnlyField.text!.value = substituteVariableValues(context.inputComponents, context.fieldInfo.defaultValue);
return readOnlyField.text?.value!;
}
});
return readOnlyField;
}
/**
* Returns a string that interpolates all variable names in the {@param inputValue} string de-marked as $(VariableName)
* substituted with their corresponding values.
*
* Only variables in the current model starting with {@see NoteBookEnvironmentVariablePrefix} are replaced.
*
* @param inputValue
* @param inputComponents
*/
function substituteVariableValues(inputComponents: InputComponents, inputValue?: string): string | undefined {
Object.keys(inputComponents)
.filter(key => key.startsWith(NoteBookEnvironmentVariablePrefix))
.forEach(key => {
const value = getInputComponentValue(inputComponents, key) ?? '<undefined>';
const re: RegExp = new RegExp(`\\\$\\\(${key}\\\)`, 'gi');
inputValue = inputValue?.replace(re, value);
});
return inputValue;
}
function processCheckboxField(context: FieldContext): void {
const checkbox = createCheckbox(context.view, { initialValue: context.fieldInfo.defaultValue! === 'true', label: context.fieldInfo.label, required: context.fieldInfo.required });
context.components.push(checkbox);
context.onNewInputComponentCreated(context.fieldInfo.variableName!, checkbox);
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: checkbox });
}
/**
* A File Picker field consists of a text field and a browse button that allows a user to pick a file system file.
* @param context The context to use to create the field
*/
function processFilePickerField(context: FieldContext, defaultFilePath?: string): FilePickerInputs {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
function processFilePickerField(context: FieldContext): FilePickerInputs {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const input = createTextInput(context.view, {
defaultValue: defaultFilePath || context.fieldInfo.defaultValue || '',
defaultValue: context.fieldInfo.defaultValue || '',
ariaLabel: context.fieldInfo.label,
required: context.fieldInfo.required,
placeHolder: context.fieldInfo.placeHolder,
width: context.fieldInfo.inputWidth,
enabled: context.fieldInfo.enabled
});
context.onNewInputComponentCreated(context.fieldInfo.variableName!, input);
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: input });
input.enabled = false;
const browseFileButton = context.view!.modelBuilder.button().withProperties({ label: loc.browse }).component();
context.onNewDisposableCreated(browseFileButton.onDidClick(async () => {
@@ -506,42 +631,22 @@ function processFilePickerField(context: FieldContext, defaultFilePath?: string)
let fileUri = fileUris[0];
input.value = fileUri.fsPath;
}));
addLabelInputPairToContainer(context.view, context.components, label, input, LabelPosition.Left, [browseFileButton]);
context.fieldInfo.labelPosition = LabelPosition.Left;
addLabelInputPairToContainer(context.view, context.components, label, input, context.fieldInfo, [browseFileButton]);
return { input: input, browseButton: browseFileButton };
}
/**
* An Kube Config Cluster picker field consists of a file system filee picker and radio button selector for cluster contexts defined in the config filed picked using the file picker.
* @param context The context to use to create the field
* This function returns a method that reads the cluster context from the {@param file}. This method then returns the cluster contexts
* read as an OptionsInfo object asynchronously.
*
* @param file - the file from which to fetch the cluster contexts
*/
async function processKubeConfigClusterPickerField(context: KubeClusterContextFieldContext): Promise<void> {
const kubeConfigFilePathVariableName = context.fieldInfo.configFileVariableName || 'AZDATA_NB_VAR_KUBECONFIG_PATH';
const filePickerContext: FieldContext = {
container: context.container,
components: context.components,
view: context.view,
onNewValidatorCreated: context.onNewValidatorCreated,
onNewDisposableCreated: context.onNewDisposableCreated,
onNewInputComponentCreated: context.onNewInputComponentCreated,
fieldInfo: {
label: loc.kubeConfigFilePath,
type: FieldType.FilePicker,
labelWidth: context.fieldInfo.labelWidth,
variableName: kubeConfigFilePathVariableName,
required: true
}
};
const filePicker = processFilePickerField(filePickerContext, getDefaultKubeConfigPath());
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
context.fieldInfo.subFields!.push({
label: filePickerContext.fieldInfo.label,
variableName: kubeConfigFilePathVariableName
});
context.onNewInputComponentCreated(kubeConfigFilePathVariableName, filePicker.input);
const getClusterContexts = async () => {
function getClusterContexts(file: string): (() => Promise<OptionsInfo>) {
return async () => {
try {
let currentClusterContext = '';
const clusterContexts: string[] = (await getKubeConfigClusterContexts(filePicker.input.value!)).map(kubeClusterContext => {
const clusterContexts: string[] = (await getKubeConfigClusterContexts(file)).map(kubeClusterContext => {
if (kubeClusterContext.isCurrentContext) {
currentClusterContext = kubeClusterContext.name;
}
@@ -551,76 +656,78 @@ async function processKubeConfigClusterPickerField(context: KubeClusterContextFi
throw Error(loc.clusterContextNotFound);
}
return { values: clusterContexts, defaultValue: currentClusterContext };
} catch (e) {
throw Error(localize('kubeConfigClusterPicker.errorLoadingClusters', "An error ocurred while loading or parsing the config file:{0}, error is:{1}", filePicker.input.value, getErrorMessage(e)));
}
catch (e) {
throw Error(localize('getClusterContexts.errorFetchingClusters', "An error ocurred while loading or parsing the config file:{0}, error is:{1}", file, getErrorMessage(e)));
}
};
createRadioOptions(context, getClusterContexts)
.then(clusterContextOptions => {
filePicker.input.onTextChanged(async () => {
await loadOrReloadRadioOptions(context, clusterContextOptions.optionsList, clusterContextOptions.loader, getClusterContexts);
});
}).catch(error => {
console.log(`failed to create radio options, Error: ${error}`);
});
}
async function processRadioOptionsTypeField(context: FieldContext): Promise<RadioOptionsInputs> {
/**
* A Kube Config Cluster picker field consists of a file system file picker and radio button selector for cluster contexts defined in the config filed picked using the file picker.
* @param context The context to use to create the field
*/
async function processKubeConfigClusterPickerField(context: KubeClusterContextFieldContext): Promise<void> {
const kubeConfigFilePathVariableName = context.fieldInfo.configFileVariableName || 'AZDATA_NB_VAR_KUBECONFIG_PATH';
const filePickerContext: FieldContext = {
container: context.container,
inputComponents: context.inputComponents,
components: context.components,
view: context.view,
onNewValidatorCreated: context.onNewValidatorCreated,
onNewDisposableCreated: context.onNewDisposableCreated,
onNewInputComponentCreated: context.onNewInputComponentCreated,
fieldInfo: {
label: loc.kubeConfigFilePath,
type: FieldType.FilePicker,
defaultValue: getDefaultKubeConfigPath(),
inputWidth: context.fieldInfo.inputWidth,
labelWidth: context.fieldInfo.labelWidth,
variableName: kubeConfigFilePathVariableName,
required: true
}
};
const filePicker = processFilePickerField(filePickerContext);
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
context.fieldInfo.subFields.push({
label: filePickerContext.fieldInfo.label,
variableName: kubeConfigFilePathVariableName
});
const radioOptionsGroup = await createRadioOptions(context, getClusterContexts(filePicker.input.value!));
context.onNewDisposableCreated(filePicker.input.onTextChanged(async () =>
await radioOptionsGroup.loadOptions(getClusterContexts(filePicker.input.value!))
));
}
async function processRadioOptionsTypeField(context: FieldContext): Promise<RadioGroupLoadingComponentBuilder> {
return await createRadioOptions(context);
}
async function createRadioOptions(context: FieldContext, getRadioButtonInfo?: (() => Promise<{ values: string[] | azdata.CategoryValue[], defaultValue: string }>))
: Promise<RadioOptionsInputs> {
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, fontWeight: context.fieldInfo.labelFontWeight });
const optionsList = context.view!.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
const radioOptionsLoadingComponent = context.view!.modelBuilder.loadingComponent().withItem(optionsList).component();
addLabelInputPairToContainer(context.view, context.components, label, radioOptionsLoadingComponent, LabelPosition.Left);
await loadOrReloadRadioOptions(context, optionsList, radioOptionsLoadingComponent, getRadioButtonInfo);
return { optionsList: optionsList, loader: radioOptionsLoadingComponent };
async function createRadioOptions(context: FieldContext, getRadioButtonInfo?: (() => Promise<OptionsInfo>))
: Promise<RadioGroupLoadingComponentBuilder> {
if (context.fieldInfo.fieldAlignItems === undefined) {
context.fieldInfo.fieldAlignItems = 'flex-start'; // by default align the items to the top.
}
const label = createLabel(context.view, { text: context.fieldInfo.label, description: context.fieldInfo.description, required: context.fieldInfo.required, width: context.fieldInfo.labelWidth, cssStyles: context.fieldInfo.labelCSSStyles });
const radioGroupLoadingComponentBuilder = new RadioGroupLoadingComponentBuilder(context.view, context.onNewDisposableCreated);
context.fieldInfo.labelPosition = LabelPosition.Left;
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: radioGroupLoadingComponentBuilder });
addLabelInputPairToContainer(context.view, context.components, label, radioGroupLoadingComponentBuilder.component(), context.fieldInfo);
const options = context.fieldInfo.options as OptionsInfo;
await radioGroupLoadingComponentBuilder.loadOptions(
getRadioButtonInfo || options); // wait for the radioGroup to be fully initialized
return radioGroupLoadingComponentBuilder;
}
async function loadOrReloadRadioOptions(context: FieldContext, optionsList: azdata.DivContainer, radioOptionsLoadingComponent: azdata.LoadingComponent, getRadioButtonInfo: (() => Promise<{ values: string[] | azdata.CategoryValue[]; defaultValue: string; }>) | undefined): Promise<void> {
radioOptionsLoadingComponent.loading = true;
optionsList.clearItems();
let options: (string[] | azdata.CategoryValue[]) = context.fieldInfo.options!;
let defaultValue: string = context.fieldInfo.defaultValue!;
try {
if (getRadioButtonInfo) {
const radioButtonsInfo = await getRadioButtonInfo();
options = radioButtonsInfo.values;
defaultValue = radioButtonsInfo.defaultValue;
}
options.forEach((op: string | azdata.CategoryValue) => {
const option: azdata.CategoryValue = (typeof op === 'string') ? { name: op, displayName: op } : op as azdata.CategoryValue;
const radioOption = context.view!.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
label: option.displayName,
checked: option.displayName === defaultValue,
name: option.name,
}).component();
if (radioOption.checked) {
context.onNewInputComponentCreated(context.fieldInfo.variableName!, radioOption);
}
context.onNewDisposableCreated(radioOption.onDidClick(() => {
// reset checked status of all remaining radioButtons
optionsList.items.filter(otherOption => otherOption !== radioOption).forEach(otherOption => (otherOption as azdata.RadioButtonComponent).checked = false);
context.onNewInputComponentCreated(context.fieldInfo.variableName!, radioOption!);
}));
optionsList.addItem(radioOption);
});
}
catch (e) {
const errorLoadingRadioOptionsLabel = context.view!.modelBuilder.text().withProperties({ value: getErrorMessage(e) }).component();
optionsList.addItem(errorLoadingRadioOptionsLabel);
}
radioOptionsLoadingComponent.loading = false;
}
/**
* An Azure Account field consists of 3 separate dropdown fields - Account, Subscription and Resource Group
* @param context The context to use to create the field
*/
function processAzureAccountField(context: AzureAccountFieldContext): void {
async function processAzureAccountField(context: AzureAccountFieldContext): Promise<void> {
context.fieldInfo.subFields = [];
const accountValueToAccountMap = new Map<string, azdata.Account>();
const subscriptionValueToSubscriptionMap = new Map<string, azureResource.AzureResourceSubscription>();
@@ -628,11 +735,12 @@ function processAzureAccountField(context: AzureAccountFieldContext): void {
const subscriptionDropdown = createAzureSubscriptionDropdown(context, subscriptionValueToSubscriptionMap);
const resourceGroupDropdown = createAzureResourceGroupsDropdown(context, accountDropdown, accountValueToAccountMap, subscriptionDropdown, subscriptionValueToSubscriptionMap);
const locationDropdown = context.fieldInfo.locations && processAzureLocationsField(context);
accountDropdown.onValueChanged(selectedItem => {
accountDropdown.onValueChanged(async selectedItem => {
const selectedAccount = accountValueToAccountMap.get(selectedItem.selected)!;
handleSelectedAccountChanged(context, selectedAccount, subscriptionDropdown, subscriptionValueToSubscriptionMap, resourceGroupDropdown, locationDropdown);
await handleSelectedAccountChanged(context, selectedAccount, subscriptionDropdown, subscriptionValueToSubscriptionMap, resourceGroupDropdown, locationDropdown);
});
azdata.accounts.getAllAccounts().then((accounts: azdata.Account[]) => {
try {
const accounts = await azdata.accounts.getAllAccounts();
// Append a blank value for the "default" option if the field isn't required, context will clear all the dropdowns when selected
const dropdownValues = context.fieldInfo.required ? [] : [''];
accountDropdown.values = dropdownValues.concat(accounts.map(account => {
@@ -641,8 +749,10 @@ function processAzureAccountField(context: AzureAccountFieldContext): void {
return displayName;
}));
const selectedAccount = accountDropdown.value ? accountValueToAccountMap.get(accountDropdown.value.toString()) : undefined;
handleSelectedAccountChanged(context, selectedAccount, subscriptionDropdown, subscriptionValueToSubscriptionMap, resourceGroupDropdown, locationDropdown);
}, (err: any) => console.log(`Unexpected error fetching accounts: ${err}`));
await handleSelectedAccountChanged(context, selectedAccount, subscriptionDropdown, subscriptionValueToSubscriptionMap, resourceGroupDropdown, locationDropdown);
} catch (error) {
vscode.window.showErrorMessage(localize('azure.accounts.unexpectedAccountsError', 'Unexpected error fetching accounts: ${0}', getErrorMessage(error)));
}
}
function createAzureAccountDropdown(context: AzureAccountFieldContext): azdata.DropDownComponent {
@@ -651,7 +761,7 @@ function createAzureAccountDropdown(context: AzureAccountFieldContext): azdata.D
description: context.fieldInfo.description,
required: context.fieldInfo.required,
width: context.fieldInfo.labelWidth,
fontWeight: context.fieldInfo.labelFontWeight
cssStyles: context.fieldInfo.labelCSSStyles
});
const accountDropdown = createDropdown(context.view, {
width: context.fieldInfo.inputWidth,
@@ -659,8 +769,9 @@ function createAzureAccountDropdown(context: AzureAccountFieldContext): azdata.D
required: context.fieldInfo.required,
label: loc.account
});
context.onNewInputComponentCreated(context.fieldInfo.variableName!, accountDropdown);
addLabelInputPairToContainer(context.view, context.components, label, accountDropdown, context.fieldInfo.labelPosition);
accountDropdown.fireOnTextChange = true;
context.onNewInputComponentCreated(context.fieldInfo.variableName!, { component: accountDropdown });
addLabelInputPairToContainer(context.view, context.components, label, accountDropdown, context.fieldInfo);
return accountDropdown;
}
@@ -671,36 +782,48 @@ function createAzureSubscriptionDropdown(
text: loc.subscription,
required: context.fieldInfo.required,
width: context.fieldInfo.labelWidth,
fontWeight: context.fieldInfo.labelFontWeight
cssStyles: context.fieldInfo.labelCSSStyles
});
const subscriptionDropdown = createDropdown(context.view, {
defaultValue: (context.fieldInfo.required) ? undefined : '',
width: context.fieldInfo.inputWidth,
editable: false,
required: context.fieldInfo.required,
label: loc.subscription
});
subscriptionDropdown.fireOnTextChange = true;
context.fieldInfo.subFields!.push({
label: label.value!,
variableName: context.fieldInfo.subscriptionVariableName
});
context.onNewInputComponentCreated(context.fieldInfo.subscriptionVariableName!, subscriptionDropdown, (inputValue: string) => {
context.onNewInputComponentCreated(context.fieldInfo.subscriptionVariableName!, {
component: subscriptionDropdown,
inputValueTransformer: (inputValue: string) => {
return subscriptionValueToSubscriptionMap.get(inputValue)?.id || inputValue;
}
});
addLabelInputPairToContainer(context.view, context.components, label, subscriptionDropdown, context.fieldInfo.labelPosition);
if (context.fieldInfo.displaySubscriptionVariableName) {
context.fieldInfo.subFields!.push({
label: label.value!,
variableName: context.fieldInfo.displaySubscriptionVariableName
});
context.onNewInputComponentCreated(context.fieldInfo.displaySubscriptionVariableName, { component: subscriptionDropdown });
}
addLabelInputPairToContainer(context.view, context.components, label, subscriptionDropdown, context.fieldInfo);
return subscriptionDropdown;
}
function handleSelectedAccountChanged(
async function handleSelectedAccountChanged(
context: AzureAccountFieldContext,
selectedAccount: azdata.Account | undefined,
subscriptionDropdown: azdata.DropDownComponent,
subscriptionValueToSubscriptionMap: Map<string, azureResource.AzureResourceSubscription>,
resourceGroupDropdown: azdata.DropDownComponent,
locationDropdown?: azdata.DropDownComponent
): void {
): Promise<void> {
subscriptionValueToSubscriptionMap.clear();
subscriptionDropdown.values = [];
handleSelectedSubscriptionChanged(context, selectedAccount, undefined, resourceGroupDropdown);
await handleSelectedSubscriptionChanged(context, selectedAccount, undefined, resourceGroupDropdown);
if (!selectedAccount) {
subscriptionDropdown.values = [''];
if (locationDropdown) {
@@ -715,7 +838,8 @@ function handleSelectedAccountChanged(
}
}
vscode.commands.executeCommand<azurecore.GetSubscriptionsResult>('azure.accounts.getSubscriptions', selectedAccount, true /*ignoreErrors*/).then(response => {
try {
const response = await vscode.commands.executeCommand<azurecore.GetSubscriptionsResult>('azure.accounts.getSubscriptions', selectedAccount, true /*ignoreErrors*/);
if (!response) {
return;
}
@@ -732,8 +856,10 @@ function handleSelectedAccountChanged(
return displayName;
}).sort((a: string, b: string) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase()));
const selectedSubscription = subscriptionDropdown.values.length > 0 ? subscriptionValueToSubscriptionMap.get(subscriptionDropdown.values[0]) : undefined;
handleSelectedSubscriptionChanged(context, selectedAccount, selectedSubscription, resourceGroupDropdown);
}, err => { vscode.window.showErrorMessage(localize('azure.accounts.unexpectedSubscriptionsError', "Unexpected error fetching subscriptions for account {0} ({1}): {2}", selectedAccount?.displayInfo.displayName, selectedAccount?.key.accountId, err.message)); });
await handleSelectedSubscriptionChanged(context, selectedAccount, selectedSubscription, resourceGroupDropdown);
} catch (error) {
vscode.window.showErrorMessage(localize('azure.accounts.unexpectedSubscriptionsError', "Unexpected error fetching subscriptions for account {0} ({1}): {2}", selectedAccount?.displayInfo.displayName, selectedAccount?.key.accountId, getErrorMessage(error)));
}
}
function createAzureResourceGroupsDropdown(
@@ -746,35 +872,42 @@ function createAzureResourceGroupsDropdown(
text: loc.resourceGroup,
required: context.fieldInfo.required,
width: context.fieldInfo.labelWidth,
fontWeight: context.fieldInfo.labelFontWeight
cssStyles: context.fieldInfo.labelCSSStyles
});
const resourceGroupDropdown = createDropdown(context.view, {
defaultValue: (context.fieldInfo.required) ? undefined : '',
width: context.fieldInfo.inputWidth,
editable: false,
required: context.fieldInfo.required,
label: loc.resourceGroup
});
resourceGroupDropdown.fireOnTextChange = true;
context.fieldInfo.subFields!.push({
label: label.value!,
variableName: context.fieldInfo.resourceGroupVariableName
});
context.onNewInputComponentCreated(context.fieldInfo.resourceGroupVariableName!, resourceGroupDropdown);
addLabelInputPairToContainer(context.view, context.components, label, resourceGroupDropdown, context.fieldInfo.labelPosition);
subscriptionDropdown.onValueChanged(selectedItem => {
const rgValueChangedEmitter = new vscode.EventEmitter<void>();
resourceGroupDropdown.onValueChanged(() => rgValueChangedEmitter.fire());
context.onNewInputComponentCreated(context.fieldInfo.resourceGroupVariableName!, { component: resourceGroupDropdown });
addLabelInputPairToContainer(context.view, context.components, label, resourceGroupDropdown, context.fieldInfo);
subscriptionDropdown.onValueChanged(async selectedItem => {
const selectedAccount = !accountDropdown || !accountDropdown.value ? undefined : accountValueToAccountMap.get(accountDropdown.value.toString());
const selectedSubscription = subscriptionValueToSubscriptionMap.get(selectedItem.selected);
handleSelectedSubscriptionChanged(context, selectedAccount, selectedSubscription, resourceGroupDropdown);
await handleSelectedSubscriptionChanged(context, selectedAccount, selectedSubscription, resourceGroupDropdown);
rgValueChangedEmitter.fire();
});
return resourceGroupDropdown;
}
function handleSelectedSubscriptionChanged(context: AzureAccountFieldContext, selectedAccount: azdata.Account | undefined, selectedSubscription: azureResource.AzureResourceSubscription | undefined, resourceGroupDropdown: azdata.DropDownComponent): void {
async function handleSelectedSubscriptionChanged(context: AzureAccountFieldContext, selectedAccount: azdata.Account | undefined, selectedSubscription: azureResource.AzureResourceSubscription | undefined, resourceGroupDropdown: azdata.DropDownComponent): Promise<void> {
resourceGroupDropdown.values = [''];
if (!selectedAccount || !selectedSubscription) {
// Don't need to execute command if we don't have both an account and subscription selected
return;
}
vscode.commands.executeCommand<azurecore.GetResourceGroupsResult>('azure.accounts.getResourceGroups', selectedAccount, selectedSubscription, true /*ignoreErrors*/).then(response => {
try {
const response = await vscode.commands.executeCommand<azurecore.GetResourceGroupsResult>('azure.accounts.getResourceGroups', selectedAccount, selectedSubscription, true /*ignoreErrors*/);
//.then(response => {
if (!response) {
return;
}
@@ -788,7 +921,9 @@ function handleSelectedSubscriptionChanged(context: AzureAccountFieldContext, se
resourceGroupDropdown.values = (response.resourceGroups.length !== 0)
? response.resourceGroups.map(resourceGroup => resourceGroup.name).sort((a: string, b: string) => a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase()))
: [''];
}, err => { vscode.window.showErrorMessage(localize('azure.accounts.unexpectedResourceGroupsError', "Unexpected error fetching resource groups for subscription {0} ({1}): {2}", selectedSubscription?.name, selectedSubscription?.id, err.message)); });
} catch (error) {
vscode.window.showErrorMessage(localize('azure.accounts.unexpectedResourceGroupsError', "Unexpected error fetching resource groups for subscription {0} ({1}): {2}", selectedSubscription?.name, selectedSubscription?.id, getErrorMessage(error)));
}
}
/**
@@ -809,7 +944,7 @@ function processAzureLocationsField(context: AzureLocationsFieldContext): azdata
text: context.fieldInfo.label || loc.location,
required: context.fieldInfo.required,
width: context.fieldInfo.labelWidth,
fontWeight: context.fieldInfo.labelFontWeight
cssStyles: context.fieldInfo.labelCSSStyles
});
const locationDropdown = createDropdown(context.view, {
width: context.fieldInfo.inputWidth,
@@ -818,14 +953,18 @@ function processAzureLocationsField(context: AzureLocationsFieldContext): azdata
label: loc.location,
values: context.fieldInfo.locations
});
locationDropdown.fireOnTextChange = true;
context.fieldInfo.subFields = context.fieldInfo.subFields || [];
if (context.fieldInfo.locationVariableName) {
context.fieldInfo.subFields!.push({
label: label.value!,
variableName: context.fieldInfo.locationVariableName
});
context.onNewInputComponentCreated(context.fieldInfo.locationVariableName, locationDropdown, (inputValue: string) => {
context.onNewInputComponentCreated(context.fieldInfo.locationVariableName, {
component: locationDropdown,
inputValueTransformer: (inputValue: string) => {
return knownAzureLocationNameMappings.get(inputValue) || inputValue;
}
});
}
if (context.fieldInfo.displayLocationVariableName) {
@@ -833,12 +972,15 @@ function processAzureLocationsField(context: AzureLocationsFieldContext): azdata
label: label.value!,
variableName: context.fieldInfo.displayLocationVariableName
});
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, locationDropdown);
context.onNewInputComponentCreated(context.fieldInfo.displayLocationVariableName, { component: locationDropdown });
}
context.onNewInputComponentCreated(context.fieldInfo.variableName!, locationDropdown, (inputValue: string) => {
context.onNewInputComponentCreated(context.fieldInfo.variableName!, {
component: locationDropdown,
inputValueTransformer: (inputValue: string) => {
return knownAzureLocationNameMappings.get(inputValue) || inputValue;
}
});
addLabelInputPairToContainer(context.view, context.components, label, locationDropdown, context.fieldInfo.labelPosition);
addLabelInputPairToContainer(context.view, context.components, label, locationDropdown, context.fieldInfo);
return locationDropdown;
}
@@ -871,10 +1013,19 @@ export function getPasswordMismatchMessage(fieldName: string): string {
export function setModelValues(inputComponents: InputComponents, model: Model): void {
Object.keys(inputComponents).forEach(key => {
let value;
const value = getInputComponentValue(inputComponents, key);
model.setPropertyValue(key, value);
});
}
function getInputComponentValue(inputComponents: InputComponents, key: string): string | undefined {
const input = inputComponents[key].component;
if ('name' in input && 'checked' in input) { //RadioButtonComponent
value = input.name;
if (input === undefined) {
return undefined;
}
let value;
if (input instanceof RadioGroupLoadingComponentBuilder) {
value = input.value;
} else if ('checked' in input) { // CheckBoxComponent
value = input.checked ? 'true' : 'false';
} else if ('value' in input) { // InputBoxComponent or DropDownComponent
@@ -887,13 +1038,11 @@ export function setModelValues(inputComponents: InputComponents, model: Model):
} else {
throw new Error(`Unknown input type with ID ${input.id}`);
}
const inputValueTransformer = inputComponents[key].inputValueTransformer;
if (inputValueTransformer) {
value = inputValueTransformer(value || '');
}
model.setPropertyValue(key, value);
});
return value;
}
export function isInputBoxEmpty(input: azdata.InputBoxComponent): boolean {

View File

@@ -4,18 +4,20 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { INotebookService } from '../../services/notebookService';
import { INotebookService, Notebook } from '../../services/notebookService';
import { IToolsService } from '../../services/toolsService';
import { Model } from '../model';
import { InputComponents, setModelValues } from '../modelViewUtils';
import { WizardBase } from '../wizardBase';
import { WizardPageBase } from '../wizardPageBase';
import { DeploymentType, NotebookWizardInfo } from './../../interfaces';
import { IPlatformService } from './../../services/platformService';
import { NotebookWizardAutoSummaryPage } from './notebookWizardAutoSummaryPage';
import { NotebookWizardPage } from './notebookWizardPage';
import { NotebookWizardSummaryPage } from './notebookWizardSummaryPage';
const localize = nls.loadMessageBundle();
export class NotebookWizard extends WizardBase<NotebookWizard, Model> {
export class NotebookWizard extends WizardBase<NotebookWizard, NotebookWizardPage, Model> {
private _inputComponents: InputComponents = {};
public get notebookService(): INotebookService {
return this._notebookService;
@@ -29,8 +31,15 @@ export class NotebookWizard extends WizardBase<NotebookWizard, Model> {
return this._wizardInfo;
}
constructor(private _wizardInfo: NotebookWizardInfo, private _notebookService: INotebookService, private _platformService: IPlatformService) {
public get inputComponents(): InputComponents {
return this._inputComponents;
}
constructor(private _wizardInfo: NotebookWizardInfo, private _notebookService: INotebookService, private _platformService: IPlatformService, private _toolsService: IToolsService) {
super(_wizardInfo.title, new Model());
if (this._wizardInfo.codeCellInsertionPosition === undefined) {
this._wizardInfo.codeCellInsertionPosition = 0;
}
this.wizardObject.doneButton.label = _wizardInfo.actionText || this.wizardObject.doneButton.label;
}
@@ -41,31 +50,63 @@ export class NotebookWizard extends WizardBase<NotebookWizard, Model> {
protected initialize(): void {
this.setPages(this.getPages());
this.wizardObject.generateScriptButton.hidden = true;
this.wizardInfo.actionText = this.wizardInfo.actionText || localize('deployCluster.ScriptToNotebook', "Script to Notebook");
this.wizardInfo.actionText = this.wizardInfo.actionText || localize('notebookWizard.ScriptToNotebook', "Script to Notebook");
this.wizardObject.doneButton.label = this.wizardInfo.actionText;
}
protected onCancel(): void {
}
protected onOk(): void {
this.model.setEnvironmentVariables();
if (this.wizardInfo.runNotebook) {
this.notebookService.backgroundExecuteNotebook(this.wizardInfo.taskName, this.wizardInfo.notebook, 'deploy', this.platformService);
} else {
this.notebookService.launchNotebook(this.wizardInfo.notebook).then(() => { }, (error) => {
vscode.window.showErrorMessage(error);
protected async onOk(): Promise<void> {
setModelValues(this.inputComponents, this.model);
const env: NodeJS.ProcessEnv = {};
this.model.setEnvironmentVariables(env, (varName) => {
const isPassword = !!this.inputComponents[varName]?.isPassword;
return isPassword;
});
const notebook: Notebook = await this.notebookService.getNotebook(this.wizardInfo.notebook);
// generate python code statements for all variables captured by the wizard
const statements = this.model.getCodeCellContentForNotebook(
this._toolsService.toolsForCurrentProvider,
(varName) => {
const isPassword = !!this.inputComponents[varName]?.isPassword;
return !isPassword;
}
);
// insert generated code statements into the notebook.
notebook.cells.splice(
this.wizardInfo.codeCellInsertionPosition ?? 0,
0,
{
cell_type: 'code',
source: statements,
metadata: {},
outputs: [],
execution_count: 0
}
);
try {
if (this.wizardInfo.runNotebook) {
this.notebookService.backgroundExecuteNotebook(this.wizardInfo.taskName, notebook, 'deploy', this.platformService, env);
} else {
Object.assign(process.env, env);
const notebookPath = this.notebookService.getNotebookPath(this.wizardInfo.notebook);
await this.notebookService.launchNotebookWithContent(notebookPath, JSON.stringify(notebook, undefined, 4));
}
} catch (error) {
vscode.window.showErrorMessage(error);
}
}
private getPages(): WizardPageBase<NotebookWizard>[] {
const pages: WizardPageBase<NotebookWizard>[] = [];
private getPages(): NotebookWizardPage[] {
const pages: NotebookWizardPage[] = [];
for (let pageIndex: number = 0; pageIndex < this.wizardInfo.pages.length; pageIndex++) {
if (this.wizardInfo.pages[pageIndex].isSummaryPage && this.wizardInfo.isSummaryPageAutoGenerated) {
// If we are auto-generating the summary page
pages.push(new NotebookWizardAutoSummaryPage(this, pageIndex));
} else {
pages.push(new NotebookWizardPage(this, pageIndex));
}
if (this.wizardInfo.generateSummaryPage) {
pages.push(new NotebookWizardSummaryPage(this));
}
return pages;
}

View File

@@ -6,19 +6,23 @@ import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { SubFieldInfo, FieldType, FontWeight, LabelPosition, SectionInfo } from '../../interfaces';
import { createSection, DefaultInputComponentWidth, DefaultLabelComponentWidth } from '../modelViewUtils';
import { WizardPageBase } from '../wizardPageBase';
import { createSection, DefaultInputWidth, DefaultLabelWidth, DefaultFieldAlignItems, DefaultFieldWidth, DefaultFieldHeight } from '../modelViewUtils';
import { NotebookWizard } from './notebookWizard';
import { NotebookWizardPage } from './notebookWizardPage';
const localize = nls.loadMessageBundle();
export class NotebookWizardSummaryPage extends WizardPageBase<NotebookWizard> {
export class NotebookWizardAutoSummaryPage extends NotebookWizardPage {
private formItems: azdata.FormComponent[] = [];
private form!: azdata.FormBuilder;
private view!: azdata.ModelView;
constructor(wizard: NotebookWizard) {
super(localize('notebookWizard.summaryPageTitle', "Review your configuration"), '', wizard);
constructor(wizard: NotebookWizard, _pageIndex: number) {
super(wizard,
_pageIndex,
wizard.wizardInfo.pages[_pageIndex].title || localize('notebookWizard.autoSummaryPageTitle', "Review your configuration"),
wizard.wizardInfo.pages[_pageIndex].description || ''
);
}
public initialize(): void {
@@ -29,25 +33,31 @@ export class NotebookWizardSummaryPage extends WizardPageBase<NotebookWizard> {
});
}
public onLeave() {
public onLeave(): void {
this.wizard.wizardObject.message = { text: '' };
}
public onEnter() {
public onEnter(): void {
this.formItems.forEach(item => {
this.form!.removeFormItem(item);
});
this.formItems = [];
const inputWidth = this.wizard.wizardInfo.inputWidth || (this.wizard.wizardInfo.summaryPage && this.wizard.wizardInfo.summaryPage.inputWidth) || DefaultInputComponentWidth;
const labelWidth = this.wizard.wizardInfo.labelWidth || (this.wizard.wizardInfo.summaryPage && this.wizard.wizardInfo.summaryPage.labelWidth) || DefaultLabelComponentWidth;
const labelPosition = this.wizard.wizardInfo.labelPosition || (this.wizard.wizardInfo.summaryPage && this.wizard.wizardInfo.summaryPage.labelPosition) || LabelPosition.Left;
const fieldWidth = this.pageInfo.fieldWidth || this.wizard.wizardInfo.fieldWidth || DefaultFieldWidth;
const fieldHeight = this.pageInfo.fieldHeight || this.wizard.wizardInfo.fieldHeight || DefaultFieldHeight;
const fieldAlignItems = this.pageInfo.fieldAlignItems || this.wizard.wizardInfo.fieldAlignItems || DefaultFieldAlignItems;
const labelWidth = this.pageInfo.labelWidth || this.wizard.wizardInfo.labelWidth || DefaultLabelWidth;
const labelPosition = this.pageInfo.labelPosition || this.wizard.wizardInfo.labelPosition || LabelPosition.Left;
const inputWidth = this.pageInfo.inputWidth || this.wizard.wizardInfo.inputWidth || DefaultInputWidth;
this.wizard.wizardInfo.pages.forEach(pageInfo => {
this.wizard.wizardInfo.pages.filter((undefined, index) => index < this._pageIndex).forEach(pageInfo => {
const summarySectionInfo: SectionInfo = {
labelPosition: labelPosition,
labelWidth: labelWidth,
inputWidth: inputWidth,
fieldWidth: fieldWidth,
fieldHeight: fieldHeight,
fieldAlignItems: fieldAlignItems,
title: '',
rows: []
};
@@ -68,6 +78,7 @@ export class NotebookWizardSummaryPage extends WizardPageBase<NotebookWizard> {
title: pageInfo.title,
component: createSection({
container: this.wizard.wizardObject,
inputComponents: this.wizard.inputComponents,
sectionInfo: summarySectionInfo,
view: this.view,
onNewDisposableCreated: () => { },
@@ -84,11 +95,11 @@ export class NotebookWizardSummaryPage extends WizardPageBase<NotebookWizard> {
private addSummaryForVariable(summarySectionInfo: SectionInfo, fieldInfo: SubFieldInfo) {
summarySectionInfo!.rows!.push({
fields: [{
items: [{
type: FieldType.ReadonlyText,
label: fieldInfo.label,
defaultValue: this.wizard.model.getStringValue(fieldInfo.variableName!),
labelFontWeight: FontWeight.Bold
labelCSSStyles: { fontWeight: FontWeight.Bold }
}]
});
}

View File

@@ -7,58 +7,73 @@ import { EOL } from 'os';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { NotebookWizardPageInfo } from '../../interfaces';
import { initializeWizardPage, InputComponents, InputComponent, setModelValues, Validator } from '../modelViewUtils';
import { initializeWizardPage, InputComponentInfo, setModelValues, Validator } from '../modelViewUtils';
import { WizardPageBase } from '../wizardPageBase';
import { NotebookWizard } from './notebookWizard';
const localize = nls.loadMessageBundle();
export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
private inputComponents: InputComponents = {};
protected get pageInfo(): NotebookWizardPageInfo {
return this.wizard.wizardInfo.pages[this._pageIndex];
}
constructor(wizard: NotebookWizard, private _pageIndex: number) {
super(wizard.wizardInfo.pages[_pageIndex].title, wizard.wizardInfo.pages[_pageIndex].description || '', wizard);
constructor(
wizard: NotebookWizard,
protected _pageIndex: number,
title?: string,
description?: string
) {
super(
wizard.wizardInfo.pages[_pageIndex].title || title || '',
wizard.wizardInfo.pages[_pageIndex].description || description || '',
wizard
);
}
public initialize(): void {
const self = this;
initializeWizardPage({
container: this.wizard.wizardObject,
inputComponents: this.wizard.inputComponents,
wizardInfo: this.wizard.wizardInfo,
pageInfo: this.pageInfo,
page: this.pageObject,
onNewDisposableCreated: (disposable: vscode.Disposable): void => {
self.wizard.registerDisposable(disposable);
this.wizard.registerDisposable(disposable);
},
onNewInputComponentCreated: (name: string, component: InputComponent): void => {
self.inputComponents[name] = { component: component };
onNewInputComponentCreated: (
name: string,
inputComponentInfo: InputComponentInfo
): void => {
if (name) {
this.wizard.inputComponents[name] = inputComponentInfo;
}
},
onNewValidatorCreated: (validator: Validator): void => {
self.validators.push(validator);
}
this.validators.push(validator);
},
});
}
public onLeave() {
setModelValues(this.inputComponents, this.wizard.model);
public onLeave(): void {
// The following callback registration clears previous navigation validators.
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
return true;
});
}
public onEnter() {
public onEnter(): void {
if (this.pageInfo.isSummaryPage) {
setModelValues(this.wizard.inputComponents, this.wizard.model);
}
this.wizard.wizardObject.registerNavigationValidator((pcInfo) => {
this.wizard.wizardObject.message = { text: '' };
if (pcInfo.newPage > pcInfo.lastPage) {
const messages: string[] = [];
this.validators.forEach(validator => {
this.validators.forEach((validator) => {
const result = validator();
if (!result.valid) {
messages.push(result.message);
@@ -67,9 +82,15 @@ export class NotebookWizardPage extends WizardPageBase<NotebookWizard> {
if (messages.length > 0) {
this.wizard.wizardObject.message = {
text: messages.length === 1 ? messages[0] : localize('wizardPage.ValidationError', "There are some errors on this page, click 'Show Details' to view the errors."),
text:
messages.length === 1
? messages[0]
: localize(
"wizardPage.ValidationError",
"There are some errors on this page, click 'Show Details' to view the errors."
),
description: messages.length === 1 ? undefined : messages.join(EOL),
level: azdata.window.MessageLevel.Error
level: azdata.window.MessageLevel.Error,
};
}
return messages.length === 0;

View File

@@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import { OptionsInfo } from '../interfaces';
import { getErrorMessage } from '../utils';
export class RadioGroupLoadingComponentBuilder implements azdata.ComponentBuilder<azdata.LoadingComponent> {
private _optionsDivContainer!: azdata.DivContainer;
private _optionsLoadingBuilder: azdata.LoadingComponentBuilder;
private _onValueChangedEmitter: vscode.EventEmitter<void> = new vscode.EventEmitter();
private _currentRadioOption!: azdata.RadioButtonComponent;
constructor(private _view: azdata.ModelView, private _onNewDisposableCreated: (disposable: vscode.Disposable) => void) {
this._optionsDivContainer = this._view!.modelBuilder.divContainer().withProperties<azdata.DivContainerProperties>({ clickable: false }).component();
this._optionsLoadingBuilder = this._view!.modelBuilder.loadingComponent().withItem(this._optionsDivContainer);
}
component(): azdata.LoadingComponent {
return this._optionsLoadingBuilder.component();
}
withProperties<U>(properties: U): azdata.ComponentBuilder<azdata.LoadingComponent> {
return this._optionsLoadingBuilder.withProperties(properties);
}
withValidation(validation: (component: azdata.LoadingComponent) => boolean): azdata.ComponentBuilder<azdata.LoadingComponent> {
return this._optionsLoadingBuilder.withValidation(validation);
}
async loadOptions(optionsInfo: OptionsInfo | (() => Promise<OptionsInfo>)): Promise<void> {
if (typeof optionsInfo !== 'object') {
optionsInfo = await optionsInfo();
}
this.component().loading = true;
this._optionsDivContainer.clearItems();
let options: (string[] | azdata.CategoryValue[]) = optionsInfo.values!;
let defaultValue: string = optionsInfo.defaultValue!;
try {
options.forEach((op: string | azdata.CategoryValue) => {
const option: azdata.CategoryValue = (typeof op === 'string')
? { name: op, displayName: op }
: op as azdata.CategoryValue;
const radioOption = this._view!.modelBuilder.radioButton().withProperties<azdata.RadioButtonProperties>({
label: option.displayName,
checked: option.displayName === defaultValue,
name: option.name,
}).component();
if (radioOption.checked) {
this._currentRadioOption = radioOption;
this._onValueChangedEmitter.fire();
}
this._onNewDisposableCreated(radioOption.onDidClick(() => {
this._optionsDivContainer.items
.filter(otherOption => otherOption !== radioOption)
.forEach(otherOption => (otherOption as azdata.RadioButtonComponent).checked = false);
this._currentRadioOption = radioOption;
this._onValueChangedEmitter.fire();
}));
this._optionsDivContainer.addItem(radioOption);
});
}
catch (e) {
const errorLoadingRadioOptionsLabel = this._view!.modelBuilder.text().withProperties({ value: getErrorMessage(e), CSSStyles: { 'color': 'Red' } }).component();
this._optionsDivContainer.addItem(errorLoadingRadioOptionsLabel);
}
this.component().loading = false;
}
get value(): string | undefined {
return this._currentRadioOption?.label;
}
get checked(): azdata.RadioButtonComponent {
return this._currentRadioOption;
}
get onValueChanged(): vscode.Event<void> {
return this._onValueChangedEmitter.event;
}
}

View File

@@ -8,7 +8,7 @@ import * as nls from 'vscode-nls';
import { AgreementInfo, DeploymentProvider, ITool, ResourceType, ToolStatus } from '../interfaces';
import { IResourceTypeService } from '../services/resourceTypeService';
import { IToolsService } from '../services/toolsService';
import { getErrorMessage, setEnvironmentVariablesForInstallPaths } from '../utils';
import { getErrorMessage } from '../utils';
import { DialogBase } from './dialogBase';
import { createFlexContainer } from './modelViewUtils';
@@ -337,7 +337,7 @@ export class ResourceTypePickerDialog extends DialogBase {
}
protected onComplete(): void {
setEnvironmentVariablesForInstallPaths(this._tools);
this.toolsService.toolsForCurrentProvider = this._tools;
this.resourceTypeService.startDeployment(this.getCurrentProvider());
}

View File

@@ -10,9 +10,9 @@ import { WizardPageBase } from './wizardPageBase';
import { Model } from './model';
const localize = nls.loadMessageBundle();
export abstract class WizardBase<T, M extends Model> {
export abstract class WizardBase<T, P extends WizardPageBase<T>, M extends Model> {
private customButtons: azdata.window.Button[] = [];
private pages: WizardPageBase<T>[] = [];
public pages: P[] = [];
public wizardObject: azdata.window.Wizard;
public toDispose: vscode.Disposable[] = [];
@@ -34,8 +34,8 @@ export abstract class WizardBase<T, M extends Model> {
newPage.onEnter();
}));
this.toDispose.push(this.wizardObject.doneButton.onClick(() => {
this.onOk();
this.toDispose.push(this.wizardObject.doneButton.onClick(async () => {
await this.onOk();
this.dispose();
}));
this.toDispose.push(this.wizardObject.cancelButton.onClick(() => {
@@ -52,14 +52,14 @@ export abstract class WizardBase<T, M extends Model> {
}
protected abstract initialize(): void;
protected abstract onOk(): void;
protected abstract async onOk(): Promise<void>;
protected abstract onCancel(): void;
public addButton(button: azdata.window.Button) {
this.customButtons.push(button);
}
protected setPages(pages: WizardPageBase<T>[]) {
protected setPages(pages: P[]) {
this.wizardObject!.pages = pages.map(p => p.pageObject);
this.pages = pages;
this.pages.forEach((page) => {

View File

@@ -2,9 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITool, NoteBookEnvironmentVariablePrefix } from './interfaces';
import * as path from 'path';
import { ToolsInstallPath } from './constants';
import { ITool, NoteBookEnvironmentVariablePrefix } from './interfaces';
export function getErrorMessage(error: any): string {
return (error instanceof Error)
@@ -21,7 +21,7 @@ export function getRuntimeBinaryPathEnvironmentVariableName(toolName: string): s
return `${NoteBookEnvironmentVariablePrefix}${toolName.toUpperCase().replace(/ |-/g, '_')}`;
}
export function setEnvironmentVariablesForInstallPaths(tools: ITool[]): void {
export function setEnvironmentVariablesForInstallPaths(tools: ITool[], env: NodeJS.ProcessEnv = process.env): void {
// Use Set class to make sure the collection only contains unique values.
let installationPaths: Set<string> = new Set<string>();
tools.forEach(t => {
@@ -30,12 +30,18 @@ export function setEnvironmentVariablesForInstallPaths(tools: ITool[]): void {
// construct an env variable name with NoteBookEnvironmentVariablePrefix prefix
// and tool.name as suffix, making sure of using all uppercase characters and only _ as separator
const envVarName = getRuntimeBinaryPathEnvironmentVariableName(t.name);
process.env[envVarName] = t.installationPathOrAdditionalInformation;
env[envVarName] = t.installationPathOrAdditionalInformation;
installationPaths.add(path.dirname(t.installationPathOrAdditionalInformation));
}
});
if (installationPaths.size > 0) {
const envVarToolsInstallationPath: string = [...installationPaths.values()].join(path.delimiter);
process.env[ToolsInstallPath] = envVarToolsInstallationPath;
env[ToolsInstallPath] = envVarToolsInstallationPath;
}
}
export function assert(condition: boolean, message?: string): asserts condition {
if (!condition) {
throw new Error(message);
}
}

View File

@@ -766,9 +766,10 @@ vscode-nls@^4.0.0:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"
integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -75,7 +75,7 @@
"mocha-multi-reporters": "^1.1.7",
"should": "^13.2.1",
"typemoq": "^2.1.0",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
},
"__metadata": {
"id": "37",

View File

@@ -808,9 +808,10 @@ vscode-nls@^4.0.0:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.1.tgz#f9916b64e4947b20322defb1e676a495861f133c"
integrity sha512-4R+2UoUUU/LdnMnFjePxfLqNhBS8lrAFyX7pjb2ud/lqDkrUavFUTcG7wR0HBZFakae0Q6KLBFjMS6W93F403A==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -6,10 +6,10 @@
"**/node_modules/**",
"**/test/**"
],
"includePid": false,
"reports": [
"cobertura",
"lcov"
"lcov",
"json"
],
"verbose": false,
"remapOptions": {

View File

@@ -269,6 +269,6 @@
"tslint": "^5.8.0",
"typemoq": "^2.1.0",
"typescript": "^2.6.1",
"vscodetestcover": "github:corivera/vscodetestcover#1.0.6"
"vscodetestcover": "^1.0.9"
}
}

View File

@@ -141,7 +141,7 @@ export class Project {
return fileEntry;
}
private createProjectEntry(relativePath: string, entryType: EntryType): ProjectEntry {
public createProjectEntry(relativePath: string, entryType: EntryType): ProjectEntry {
return new ProjectEntry(Uri.file(path.join(this.projectFolderPath, relativePath)), relativePath, entryType);
}

View File

@@ -22,7 +22,7 @@ export class FolderNode extends BaseProjectTreeItem {
}
public get children(): BaseProjectTreeItem[] {
return Object.values(this.fileChildren).sort();
return Object.values(this.fileChildren).sort(sortFileFolderNodes);
}
public get treeItem(): vscode.TreeItem {
@@ -64,6 +64,23 @@ export class FileNode extends BaseProjectTreeItem {
}
}
/**
* Compares two folder/file tree nodes so that folders come before files, then alphabetically
* @param a a folder or file tree node
* @param b another folder or file tree node
*/
export function sortFileFolderNodes(a: (FolderNode | FileNode), b: (FolderNode | FileNode)): number {
if (a instanceof FolderNode && !(b instanceof FolderNode)) {
return -1;
}
else if (!(a instanceof FolderNode) && b instanceof FolderNode) {
return 1;
}
else {
return a.uri.fsPath.localeCompare(b.uri.fsPath);
}
}
/**
* Converts a full filesystem URI to a project-relative URI that's compatible with the project tree
*/
@@ -75,8 +92,7 @@ function fsPathToProjectUri(fileSystemUri: vscode.Uri, projectNode: ProjectRootT
localUri = fileSystemUri.fsPath.substring(projBaseDir.length);
}
else {
vscode.window.showErrorMessage('Project pointing to file outside of directory');
throw new Error('Project pointing to file outside of directory');
throw new Error(`Project (${projBaseDir}) pointing to file outside of directory (${fileSystemUri.fsPath})`);
}
return vscode.Uri.file(path.join(projectNode.uri.path, localUri));

View File

@@ -32,14 +32,7 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
const output: BaseProjectTreeItem[] = [];
output.push(this.dataSourceNode);
// sort children so that folders come first, then alphabetical
const sortedChildren = Object.values(this.fileChildren).sort((a: (fileTree.FolderNode | fileTree.FileNode), b: (fileTree.FolderNode | fileTree.FileNode)) => {
if (a instanceof fileTree.FolderNode && !(b instanceof fileTree.FolderNode)) { return -1; }
else if (!(a instanceof fileTree.FolderNode) && b instanceof fileTree.FolderNode) { return 1; }
else { return a.uri.fsPath.localeCompare(b.uri.fsPath); }
});
return output.concat(sortedChildren);
return output.concat(Object.values(this.fileChildren).sort(fileTree.sortFileFolderNodes));
}
public get treeItem(): vscode.TreeItem {
@@ -53,6 +46,10 @@ export class ProjectRootTreeItem extends BaseProjectTreeItem {
for (const entry of this.project.files) {
const parentNode = this.getEntryParentNode(entry);
if (Object.keys(parentNode.fileChildren).includes(path.basename(entry.fsUri.path))) {
continue; // ignore duplicate entries
}
let newNode: fileTree.FolderNode | fileTree.FileNode;
switch (entry.type) {

View File

@@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as should from 'should';
import * as vscode from 'vscode';
import * as os from 'os';
import * as path from 'path';
import { Project, EntryType } from '../models/project';
import { FolderNode, FileNode, sortFileFolderNodes } from '../models/tree/fileFolderTreeItem';
import { ProjectRootTreeItem } from '../models/tree/projectTreeItem';
describe('Project Tree tests', function (): void {
it('Should correctly order tree nodes by type, then by name', async function (): Promise<void> {
const root = os.platform() === 'win32' ? 'Z:\\' : '/';
const parent = new ProjectRootTreeItem(new Project(vscode.Uri.file(`${root}Fake.sqlproj`).fsPath));
let inputNodes: (FileNode | FolderNode)[] = [
new FileNode(vscode.Uri.file(`${root}C`), parent),
new FileNode(vscode.Uri.file(`${root}D`), parent),
new FolderNode(vscode.Uri.file(`${root}Z`), parent),
new FolderNode(vscode.Uri.file(`${root}X`), parent),
new FileNode(vscode.Uri.file(`${root}B`), parent),
new FileNode(vscode.Uri.file(`${root}A`), parent),
new FolderNode(vscode.Uri.file(`${root}W`), parent),
new FolderNode(vscode.Uri.file(`${root}Y`), parent)
];
inputNodes = inputNodes.sort(sortFileFolderNodes);
const expectedNodes: (FileNode | FolderNode)[] = [
new FolderNode(vscode.Uri.file(`${root}W`), parent),
new FolderNode(vscode.Uri.file(`${root}X`), parent),
new FolderNode(vscode.Uri.file(`${root}Y`), parent),
new FolderNode(vscode.Uri.file(`${root}Z`), parent),
new FileNode(vscode.Uri.file(`${root}A`), parent),
new FileNode(vscode.Uri.file(`${root}B`), parent),
new FileNode(vscode.Uri.file(`${root}C`), parent),
new FileNode(vscode.Uri.file(`${root}D`), parent)
];
should(inputNodes.map(n => n.uri.path)).deepEqual(expectedNodes.map(n => n.uri.path));
});
it('Should build tree from Project file correctly', async function (): Promise<void> {
const root = os.platform() === 'win32' ? 'Z:\\' : '/';
const proj = new Project(vscode.Uri.file(`${root}TestProj.sqlproj`).fsPath);
// nested entries before explicit top-level folder entry
// also, ordering of files/folders at all levels
proj.files.push(proj.createProjectEntry(path.join('someFolder', 'bNestedTest.sql'), EntryType.File));
proj.files.push(proj.createProjectEntry(path.join('someFolder', 'bNestedFolder'), EntryType.Folder));
proj.files.push(proj.createProjectEntry(path.join('someFolder', 'aNestedTest.sql'), EntryType.File));
proj.files.push(proj.createProjectEntry(path.join('someFolder', 'aNestedFolder'), EntryType.Folder));
proj.files.push(proj.createProjectEntry('someFolder', EntryType.Folder));
// duplicate files
proj.files.push(proj.createProjectEntry('duplicate.sql', EntryType.File));
proj.files.push(proj.createProjectEntry('duplicate.sql', EntryType.File));
// duplicate folders
proj.files.push(proj.createProjectEntry('duplicateFolder', EntryType.Folder));
proj.files.push(proj.createProjectEntry('duplicateFolder', EntryType.Folder));
const tree = new ProjectRootTreeItem(proj);
should(tree.children.map(x => x.uri.path)).deepEqual([
'/TestProj.sqlproj/Data Sources',
'/TestProj.sqlproj/duplicateFolder',
'/TestProj.sqlproj/someFolder',
'/TestProj.sqlproj/duplicate.sql']);
should(tree.children.find(x => x.uri.path === '/TestProj.sqlproj/someFolder')?.children.map(y => y.uri.path)).deepEqual([
'/TestProj.sqlproj/someFolder/aNestedFolder',
'/TestProj.sqlproj/someFolder/bNestedFolder',
'/TestProj.sqlproj/someFolder/aNestedTest.sql',
'/TestProj.sqlproj/someFolder/bNestedTest.sql']);
});
});

View File

@@ -901,9 +901,10 @@ vscode-nls@^3.2.1:
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
"vscodetestcover@github:corivera/vscodetestcover#1.0.6":
version "1.0.5"
resolved "https://codeload.github.com/corivera/vscodetestcover/tar.gz/14e0f2c46346b31bc1af2c590febeaf69a9112eb"
vscodetestcover@^1.0.9:
version "1.0.9"
resolved "https://registry.yarnpkg.com/vscodetestcover/-/vscodetestcover-1.0.9.tgz#0191f403dd59ba1153fc57979e281e992ce63731"
integrity sha512-8z2961KF9Tuz5XdHAC6RMV3CrzAoUcfIK7wLYjLIXD4dbHIT7ceZMhoxToW1olyi3pFnThlS4lRXtx8Q5iyMMQ==
dependencies:
decache "^4.4.0"
glob "^7.1.2"

View File

@@ -10,7 +10,7 @@
"aiKey": "AIF-37eefaf0-8022-4671-a3fb-64752724682e",
"engines": {
"vscode": "*",
"azdata": ">1.10.0"
"azdata": ">=1.19.0"
},
"repository": {
"type": "git",
@@ -23,6 +23,257 @@
],
"contributes": {
"resourceDeploymentTypes": [
{
"name": "test-wizard",
"displayName": "%resource.type.wizard.display.name%",
"description": "%resource.type.wizard.description%",
"platforms": "*",
"icon": {
"light": "./images/book.svg",
"dark": "./images/book_inverse.svg"
},
"providers": [
{
"notebookWizard": {
"notebook": "%deployment-notebook-1%",
"type": "new-arc-control-plane",
"runNotebook": false,
"codeCellInsertionPosition": 1,
"actionText": "%deploy.wizard.action%",
"title": "%wizard.new.wizard.title%",
"name": "wizard.new.wizard",
"labelPosition": "left",
"generateSummaryPage": false,
"pages": [
{
"title": "%wizard.select.cluster.title%",
"sections": [
{
"fields": [
{
"type": "kube_cluster_context_picker",
"label": "%wizard.kube.cluster.context%",
"required": true,
"inputWidth": "350px",
"variableName": "AZDATA_NB_VAR_CLUSTER_CONTEXT",
"configFileVariableName": "AZDATA_NB_VAR_CONFIG_FILE"
}
]
}
]
},
{
"title": "%wizard.cluster.config.profile.title%",
"sections": [
{
"fields": [
{
"type": "readonly_text",
"label": "%wizard.project.details.description%",
"labelWidth": "600px"
},
{
"type": "options",
"label": "%wizard.cluster.config.profile%",
"required": true,
"variableName": "AZDATA_NB_VAR_PROFILE",
"editable": false,
"options": {
"values":[
"aks-dev-test",
"aks-dev-test-ha",
"aks-dev-test",
"aks-private-preview",
"kubeadm-dev-test",
"kubeadm-private-preview"
],
"defaultValue": "aks-dev-test",
"optionsType": "radio"
}
},
{
"label": "%wizard.dropdown.options.field%",
"variableName": "AZDATA_NB_VAR_DROPDOWN_OPTIONS",
"type": "options",
"options": {
"values": ["1","2","3"],
"defaultValue": "2",
"optionsType": "dropdown"
}
}
]
}
]
},
{
"title": "%wizard.data.controller.create.summary.title%",
"isSummaryPage": true,
"fieldHeight": "16px",
"sections": [
{
"title": "",
"collapsible": false,
"fieldWidth": "200px",
"fieldHeight": "12px",
"spaceBetweenFields": 0,
"rows": [
{
"items": [
{
"items": [
{
"label": "%wizard.summary.data.controller%",
"type": "readonly_text",
"enabled": true,
"labelWidth": "185px"
}
]
},
{
"items": [
{
"label": "%wizard.summary.estimated.cost.per.month%",
"type": "readonly_text",
"enabled": true,
"labelWidth": "190px",
"labelCSSStyles": {
"fontWeight": "Bold"
}
}
]
}
]
},
{
"items": [
{
"items": [
{
"label": "%wizard.summary.by.contoso%",
"type": "readonly_text",
"labelWidth": "185px"
}
]
},
{
"items": [
{
"label": "%wizard.summary.free%",
"type": "readonly_text",
"enabled": true,
"defaultValue": "",
"labelWidth": "100px"
}
]
}
]
},
{
"items": [
{
"items": [
{
"label": "{0}",
"type": "readonly_text",
"enabled": true,
"labelCSSStyles": { "color": "#0078D4" },
"labelWidth": "67px",
"links": [
{
"text": "%wizard.summary.terms.of.use%",
"url": "https://aka.ms/eula-azdata-en"
}
]
},
{
"label": "|",
"type": "readonly_text",
"enabled": true,
"defaultValue": "",
"labelWidth": "4px",
"fieldWidth": "6px"
},
{
"label": "{0}",
"type": "readonly_text",
"enabled": true,
"labelCSSStyles": { "color": "#0078D4" },
"labelWidth": "102px",
"links": [
{
"text": "%wizard.summary.terms.privacy.policy%",
"url": "https://go.contoso.com/fwlink/?LinkId=853010"
}
]
}
]
}
]
}
]
},
{
"title": "%wizard.summary.terms%",
"fieldHeight": "88px",
"fields":[
{
"label": "%wizard.summary.terms.description%",
"type": "readonly_text",
"enabled": true,
"labelWidth": "750px"
}
]
},
{
"title": "%wizard.summary.kubernetes%",
"fields":[
{
"label": "%wizard.summary.kube.config.file.path%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_CONFIG_FILE)"
},
{
"label": "%wizard.summary.cluster.context%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_CLUSTER_CONTEXT)"
},
{
"label": "%wizard.summary.profile%",
"type": "readonly_text",
"isEvaluated": true,
"defaultValue": "$(AZDATA_NB_VAR_PROFILE)"
}
]
}
]
}
]
},
"requiredTools": [
{
"name": "kubectl"
}
],
"when": true
}
],
"agreement": {
"template": "%wizard.data.controller.agreement%",
"links": [
{
"text": "%contoso.agreement.privacy.statement%",
"url": "https://go.contoso.com/fwlink/?LinkId=853010"
},
{
"text": "%wizard.agreement.contosoCmd.eula%",
"url": "https://aka.ms/eula-contosoCmd-en"
}
]
}
},
{
"name": "x-data-service",
"displayName": "%resource-type-display-name%",
@@ -81,6 +332,14 @@
"defaultValue": "",
"required": true
},
{
"type": "kube_cluster_context_picker",
"label": "%kube.cluster.context%",
"required": true,
"inputWidth": "350px",
"variableName": "AZDATA_NB_VAR_CLUSTER_CONTEXT",
"configFileVariableName": "AZDATA_NB_VAR_CONFIG_FILE"
},
{
"label": "%number-field%",
"variableName": "AZDATA_NB_VAR_NUMBER",
@@ -159,7 +418,7 @@
"links": [
{
"text": "%agreement-1-name%",
"url": "https://www.microsoft.com"
"url": "https://www.contoso.com"
},
{
"text": "%agreement-2-name%",

View File

@@ -12,9 +12,41 @@
"deployment-notebook-2": "./notebooks/deploy-x-data-service-2.ipynb",
"text-field": "text field",
"password-field": "password field",
"kube.cluster.context": "Kube cluster context",
"number-field": "numeric field",
"confirm-password": "confirm password",
"agreement": "I accept {0} and {1}.",
"agreement-1-name": "Agreement 1",
"agreement-2-name": "Agreement 2"
"agreement-2-name": "Agreement 2",
"resource.type.wizard.display.name": "Test controller",
"resource.type.wizard.description": "Creates a Test controller",
"wizard.new.wizard.title": "Create Test controller",
"wizard.cluster.environment.title": "What is your target existing Kubernetes cluster environment?",
"wizard.select.cluster.title": "Select from installed existing Kubernetes clusters",
"wizard.kube.cluster.context": "Cluster context",
"wizard.cluster.config.profile.title": "Choose the config profile",
"wizard.cluster.config.profile": "Config profile",
"wizard.dropdown.options.field": "dropdown field",
"wizard.project.details.title": "Project details",
"wizard.project.details.description": "Project details for Contoso corporation",
"wizard.data.controller.create.summary.title": "Review your configuration",
"wizard.summary.data.controller": "Test controller",
"wizard.summary.estimated.cost.per.month": "Estimated cost per month",
"wizard.summary.by.contoso" : "by Contoso",
"wizard.summary.free" : "Free",
"wizard.summary.terms.of.use" : "Terms of use",
"wizard.summary.terms.privacy.policy" : "Privacy policy",
"wizard.summary.terms" : "Terms",
"wizard.summary.terms.description": "By clicking 'Script to notebook', I (a) agree to the legal terms and privacy statement(s) associated with the doing business with Contoso.",
"wizard.summary.kubernetes": "Kubernetes",
"wizard.summary.kube.config.file.path": "Kube config file path",
"wizard.summary.cluster.context": "Cluster context",
"wizard.summary.profile": "Config profile",
"wizard.data.controller.agreement": "I accept {0} and {1}.",
"contoso.agreement.privacy.statement":"contoso Privacy Statement",
"wizard.agreement.contosoCmd.eula":"contoso cmd license terms",
"deploy.wizard.action":"Script to notebook"
}

View File

@@ -47,7 +47,7 @@ if "%SKIP_PYTHON_INSTALL_TEST%" == "1" (
)
call %INTEGRATION_TEST_ELECTRON_PATH% --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 ^
--extensionDevelopmentPath=%~dp0\..\extensions\integration-tests --extensionTestsPath=%~dp0\..\extensions\integration-tests\out\tests --disable-telemetry --disable-crash-reporter --disable-updates -nogpu
--extensionDevelopmentPath=%~dp0\..\extensions\integration-tests --extensionTestsPath=%~dp0\..\extensions\integration-tests\out\test --disable-telemetry --disable-crash-reporter --disable-updates -nogpu
rmdir /s /q %VSCODEUSERDATADIR%
rmdir /s /q %VSCODEEXTENSIONSDIR%

View File

@@ -91,10 +91,10 @@ REM echo *** starting mssql tests ***
REM echo ******************************************
REM call "%INTEGRATION_TEST_ELECTRON_PATH%" --extensionDevelopmentPath=%~dp0\..\extensions\mssql --extensionTestsPath=%~dp0\..\extensions\mssql\out\test --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 --disable-telemetry --disable-crash-reporter --disable-updates --nogpu
echo ********************************************
echo *** starting sql-database-projects tests ***
echo ********************************************
call "%INTEGRATION_TEST_ELECTRON_PATH%" --extensionDevelopmentPath=%~dp0\..\extensions\sql-database-projects --extensionTestsPath=%~dp0\..\extensions\sql-database-projects\out\test --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 --disable-telemetry --disable-crash-reporter --disable-updates --nogpu
REM echo ********************************************
REM echo *** starting sql-database-projects tests ***
REM echo ********************************************
REM call "%INTEGRATION_TEST_ELECTRON_PATH%" --extensionDevelopmentPath=%~dp0\..\extensions\sql-database-projects --extensionTestsPath=%~dp0\..\extensions\sql-database-projects\out\test --user-data-dir=%VSCODEUSERDATADIR% --extensions-dir=%VSCODEEXTENSIONSDIR% --remote-debugging-port=9222 --disable-telemetry --disable-crash-reporter --disable-updates --nogpu
if %errorlevel% neq 0 exit /b %errorlevel%

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>Artboard 10</title>
<g>
<path d="M4,6h7V3H4ZM5,4h5V5H5Z"/>
<path d="M3,0a1.732,1.732,0,0,0-.742.168,2.256,2.256,0,0,0-1.09,1.09A1.735,1.735,0,0,0,1,2V14a1.947,1.947,0,0,0,.156.777,2.018,2.018,0,0,0,1.067,1.067A1.947,1.947,0,0,0,3,16H14V0ZM3,15a.972.972,0,0,1-.391-.078,1.023,1.023,0,0,1-.531-.531,1.019,1.019,0,0,1,0-.782,1.024,1.024,0,0,1,.215-.316,1.012,1.012,0,0,1,.316-.215A.972.972,0,0,1,3,13H13v2Zm10-3H3a1.836,1.836,0,0,0-.523.074A2.194,2.194,0,0,0,2,12.281V2a.8.8,0,0,1,.09-.359,1.223,1.223,0,0,1,.23-.321,1.246,1.246,0,0,1,.321-.23A.792.792,0,0,1,3,1H13Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 684 B

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,11 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M4 6H11V3H4V6ZM5 4H10V5H5V4Z" fill="white"/>
<path d="M2.99998 0C2.74326 0.000315985 2.48981 0.0577002 2.25798 0.168C1.77794 0.392083 1.39207 0.777956 1.16798 1.258C1.05778 1.48986 1.0004 1.74328 0.999984 2V14C0.998165 14.267 1.05127 14.5314 1.15598 14.777C1.36029 15.2573 1.74272 15.6397 2.22298 15.844C2.46855 15.9487 2.73303 16.0018 2.99998 16H14V0H2.99998ZM2.99998 15C2.86566 15.0013 2.73253 14.9747 2.60898 14.922C2.37107 14.8187 2.18127 14.6289 2.07798 14.391C2.02649 14.2671 1.99998 14.1342 1.99998 14C1.99998 13.8658 2.02649 13.7329 2.07798 13.609C2.12918 13.4911 2.20212 13.3839 2.29298 13.293C2.38371 13.2019 2.49095 13.1289 2.60898 13.078C2.73253 13.0253 2.86566 12.9987 2.99998 13H13V15H2.99998ZM13 12H2.99998C2.823 11.9993 2.64684 12.0243 2.47698 12.074C2.31034 12.1238 2.15022 12.1933 1.99998 12.281V2C2.00151 1.87494 2.03233 1.75199 2.08998 1.641C2.14876 1.52225 2.22643 1.41383 2.31998 1.32C2.41408 1.22676 2.52244 1.14912 2.64098 1.09C2.75186 1.03207 2.87489 1.00123 2.99998 1H13V12Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -545,6 +545,13 @@ Includes non-masked style declarations. */
background-image: url("database_colored.svg");
}
.book.codicon {
-webkit-mask-image: url("book_image.svg");
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: 50% 50%;
}
.small {
width: 16px;
height: 16px;

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.
*--------------------------------------------------------------------------------------------*/
/* Activity Bar */
.monaco-workbench .activitybar .monaco-action-bar .action-label.book {
background-color: rgba(255, 255, 255, 0.4);
}
/* Activity Bar */
.monaco-workbench .activitybar .monaco-action-bar .checked .action-label.book {
background-color: rgb(255, 255, 255);
}

View File

@@ -46,6 +46,9 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr
import { NotebookThemingContribution } from 'sql/workbench/contrib/notebook/browser/notebookThemingContribution';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { ToggleTabFocusModeAction } from 'vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode';
import { NotebookExplorerViewletViewsContribution, OpenNotebookExplorerViewletAction } from 'sql/workbench/contrib/notebook/browser/notebookExplorer/notebookExplorerViewlet';
import 'vs/css!./media/notebook.contribution';
Registry.as<IEditorInputFactoryRegistry>(EditorInputFactoryExtensions.EditorInputFactories)
.registerEditorInputFactory(FileNotebookInput.ID, FileNoteBookEditorInputFactory);
@@ -348,3 +351,16 @@ registerComponentType({
selector: MimeRendererComponent.SELECTOR
});
registerCellComponent(TextCellComponent);
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(NotebookExplorerViewletViewsContribution, LifecyclePhase.Starting);
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionsExtensions.WorkbenchActions);
registry.registerWorkbenchAction(
SyncActionDescriptor.create(
OpenNotebookExplorerViewletAction,
OpenNotebookExplorerViewletAction.ID,
OpenNotebookExplorerViewletAction.LABEL,
{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_B }),
'View: Show Notebook Explorer',
localize('notebookExplorer.view', "View")
);

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