Merge remote-tracking branch 'origin/master' into ads-master-vscode-2020-05-31T19-47-47
10
.github/workflows/ci.yml
vendored
@@ -51,16 +51,14 @@ jobs:
|
||||
name: Run Unit Tests (Electron)
|
||||
- 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
|
||||
# {{SQL CARBON EDIT}} Add coveralls. We merge first to get around issue where parallel builds weren't being combined correctly
|
||||
- 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
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"ignorePatterns": [
|
||||
"**/node_modules/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura"
|
||||
],
|
||||
@@ -14,4 +13,4 @@
|
||||
"basePath": ".",
|
||||
"useAbsolutePaths": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
21
extensions/integration-tests/coverConfig.json
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
48
extensions/integration-tests/src/test/index.ts
Normal 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;
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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'/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"requiredPythonPackages": [
|
||||
{
|
||||
"name": "sqlmlutils",
|
||||
"version": "1.0.0"
|
||||
"version": "1.0.1"
|
||||
}
|
||||
],
|
||||
"requiredRPackages": [
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}")`;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 |
@@ -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';
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
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);
|
||||
connectionProfile.options[USER] = await controller.getKnoxUsername(connectionProfile.userName);
|
||||
} catch (err) {
|
||||
console.log(`Unexpected error getting Knox username for Spark kernel: ${err}`);
|
||||
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, 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({
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"relativeSourcePath": "..",
|
||||
"relativeCoverageDir": "../../coverage",
|
||||
"ignorePatterns": [
|
||||
"**/node_modules/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
"basePath": ".",
|
||||
"useAbsolutePaths": true
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
let initialContent = document.getText();
|
||||
return azdata.nb.showNotebookDocument(untitledFileName, {
|
||||
connectionProfile: undefined,
|
||||
preview: false,
|
||||
initialContent: initialContent,
|
||||
initialDirtyState: false
|
||||
});
|
||||
const document = await vscode.workspace.openTextDocument(notebookPath);
|
||||
let initialContent = document.getText();
|
||||
return await azdata.nb.showNotebookDocument(untitledFileName, {
|
||||
connectionProfile: undefined,
|
||||
preview: false,
|
||||
initialContent: initialContent,
|
||||
initialDirtyState: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
process.env[propertyName] = value === undefined ? '' : 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}`);
|
||||
}
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
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: context.fieldInfo.options,
|
||||
defaultValue: context.fieldInfo.defaultValue,
|
||||
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) => {
|
||||
return subscriptionValueToSubscriptionMap.get(inputValue)?.id || inputValue;
|
||||
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) => {
|
||||
return knownAzureLocationNameMappings.get(inputValue) || inputValue;
|
||||
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) => {
|
||||
return knownAzureLocationNameMappings.get(inputValue) || inputValue;
|
||||
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,31 +1013,38 @@ export function getPasswordMismatchMessage(fieldName: string): string {
|
||||
|
||||
export function setModelValues(inputComponents: InputComponents, model: Model): void {
|
||||
Object.keys(inputComponents).forEach(key => {
|
||||
let value;
|
||||
const input = inputComponents[key].component;
|
||||
if ('name' in input && 'checked' in input) { //RadioButtonComponent
|
||||
value = input.name;
|
||||
} else if ('checked' in input) { // CheckBoxComponent
|
||||
value = input.checked ? 'true' : 'false';
|
||||
} else if ('value' in input) { // InputBoxComponent or DropDownComponent
|
||||
const inputValue = input.value;
|
||||
if (typeof inputValue === 'string' || typeof inputValue === 'undefined') {
|
||||
value = inputValue;
|
||||
} else {
|
||||
value = inputValue.name;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown input type with ID ${input.id}`);
|
||||
}
|
||||
|
||||
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
||||
if (inputValueTransformer) {
|
||||
value = inputValueTransformer(value || '');
|
||||
}
|
||||
const value = getInputComponentValue(inputComponents, key);
|
||||
model.setPropertyValue(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
function getInputComponentValue(inputComponents: InputComponents, key: string): string | undefined {
|
||||
const input = inputComponents[key].component;
|
||||
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
|
||||
const inputValue = input.value;
|
||||
if (typeof inputValue === 'string' || typeof inputValue === 'undefined') {
|
||||
value = inputValue;
|
||||
} else {
|
||||
value = inputValue.name;
|
||||
}
|
||||
} else {
|
||||
throw new Error(`Unknown input type with ID ${input.id}`);
|
||||
}
|
||||
const inputValueTransformer = inputComponents[key].inputValueTransformer;
|
||||
if (inputValueTransformer) {
|
||||
value = inputValueTransformer(value || '');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function isInputBoxEmpty(input: azdata.InputBoxComponent): boolean {
|
||||
return input.value === undefined || input.value === '';
|
||||
}
|
||||
|
||||
@@ -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++) {
|
||||
pages.push(new NotebookWizardPage(this, pageIndex));
|
||||
}
|
||||
if (this.wizardInfo.generateSummaryPage) {
|
||||
pages.push(new NotebookWizardSummaryPage(this));
|
||||
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));
|
||||
}
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}]
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"**/node_modules/**",
|
||||
"**/test/**"
|
||||
],
|
||||
"includePid": false,
|
||||
"reports": [
|
||||
"cobertura",
|
||||
"lcov"
|
||||
"lcov",
|
||||
"json"
|
||||
],
|
||||
"verbose": false,
|
||||
"remapOptions": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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']);
|
||||
});
|
||||
});
|
||||
@@ -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"
|
||||
|
||||
@@ -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%",
|
||||
|
||||
@@ -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"
|
||||
|
||||
}
|
||||
|
||||
@@ -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%
|
||||
|
||||
@@ -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%
|
||||
|
||||
|
||||
7
src/sql/media/icons/book.svg
Normal 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 |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
11
src/sql/media/icons/book_inverse.svg
Normal 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 |
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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")
|
||||
);
|
||||
|
||||