mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8eb7bec1b | ||
|
|
565b7404f9 | ||
|
|
9cffe4d476 | ||
|
|
43be88a37c | ||
|
|
ea67859de7 | ||
|
|
c8986464ec | ||
|
|
7804f94d8b | ||
|
|
bfa77aebfc | ||
|
|
487fb02313 | ||
|
|
ef64038107 | ||
|
|
5d336accbc | ||
|
|
99047b2866 | ||
|
|
f611cf3b5a | ||
|
|
4ad059605c | ||
|
|
dc2ff97dd8 | ||
|
|
2b5265c103 | ||
|
|
2e98fde053 | ||
|
|
d5176e0eb7 | ||
|
|
eb0b2a847b | ||
|
|
cff5482f69 | ||
|
|
afc37973d0 | ||
|
|
3eada6c6ab | ||
|
|
7c39268fe5 | ||
|
|
eb67b299de | ||
|
|
3e7a09c1e3 | ||
|
|
637dc9b9b2 | ||
|
|
1de16d4715 | ||
|
|
49090d774d | ||
|
|
9a695b5cdd | ||
|
|
e0339b50c0 | ||
|
|
d0c584672f | ||
|
|
27816acaeb | ||
|
|
4de3cc8a09 | ||
|
|
5c16ceb2fa | ||
|
|
9db3f73413 | ||
|
|
e0ceddce09 | ||
|
|
6dc4096299 | ||
|
|
1fa03b5c74 | ||
|
|
f8f57a93c3 | ||
|
|
960fe63312 | ||
|
|
7545b94128 | ||
|
|
1263a27c1c | ||
|
|
e1c084d365 | ||
|
|
7465ec0bbd | ||
|
|
17ed57836f | ||
|
|
d0acb51fd7 | ||
|
|
71c1ed6c49 | ||
|
|
bfb68254a4 | ||
|
|
18f7662209 | ||
|
|
a0d84f383c | ||
|
|
1f447ae681 | ||
|
|
8bd6691331 | ||
|
|
42afcf9322 | ||
|
|
3d3694bb8d | ||
|
|
589b913960 | ||
|
|
7ba4f42494 | ||
|
|
c96118d2b5 | ||
|
|
0285d8cd38 | ||
|
|
ee87604a4d | ||
|
|
2235ebaf20 | ||
|
|
954d0d954f | ||
|
|
e31747d087 | ||
|
|
fc581253a4 | ||
|
|
47c4609f23 | ||
|
|
2d52bc2a49 | ||
|
|
5367101330 | ||
|
|
db145b4999 | ||
|
|
7f950ddb80 | ||
|
|
50e2251e74 | ||
|
|
33d5455b6f | ||
|
|
ac018500cd | ||
|
|
3e661db283 | ||
|
|
18fb78b3ec | ||
|
|
58ff13d399 |
34
.vscode/launch.json
vendored
34
.vscode/launch.json
vendored
@@ -92,6 +92,30 @@
|
|||||||
"webRoot": "${workspaceFolder}",
|
"webRoot": "${workspaceFolder}",
|
||||||
"timeout": 45000
|
"timeout": 45000
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch azuredatastudio with new notebook command",
|
||||||
|
"windows": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/scripts/sql.bat"
|
||||||
|
},
|
||||||
|
"osx": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/scripts/sql.sh"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"runtimeExecutable": "${workspaceFolder}/scripts/sql.sh"
|
||||||
|
},
|
||||||
|
"urlFilter": "*index.html*",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"--inspect=5875",
|
||||||
|
"--command=notebook.command.new"
|
||||||
|
],
|
||||||
|
"skipFiles": [
|
||||||
|
"**/winjs*.js"
|
||||||
|
],
|
||||||
|
"webRoot": "${workspaceFolder}",
|
||||||
|
"timeout": 45000
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
@@ -128,6 +152,16 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"--extensionDevelopmentPath=${workspaceRoot}/extensions/debug-auto-launch"
|
"--extensionDevelopmentPath=${workspaceRoot}/extensions/debug-auto-launch"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Smoke Test",
|
||||||
|
"program": "${workspaceFolder}/test/smoke/test/index.js",
|
||||||
|
"cwd": "${workspaceFolder}/test/smoke",
|
||||||
|
"env": {
|
||||||
|
"BUILD_ARTIFACTSTAGINGDIRECTORY": "${workspaceFolder}"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"compounds": [
|
"compounds": [
|
||||||
|
|||||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,5 +1,27 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## Version 1.3.8
|
||||||
|
* Release date: January 9, 2019
|
||||||
|
* Release status: General Availability
|
||||||
|
|
||||||
|
## What's new in this version
|
||||||
|
* #13 Feature Request: Azure Active Directory Authentication
|
||||||
|
* #1040 Stream initial query results as they become available
|
||||||
|
* #3298 Сan't add an azure account.
|
||||||
|
* #2387 Support Per-User Installer
|
||||||
|
* SQL Server Import updates for DACPAC\BACPAC
|
||||||
|
* SQL Server Profiler UI and UX improvements
|
||||||
|
* Updates to [SQL Server 2019 extension](https://docs.microsoft.com/sql/azure-data-studio/sql-server-2019-extension?view=sql-server-ver15)
|
||||||
|
* **sp_executesql to SQL** and **New Database** extensions
|
||||||
|
|
||||||
|
## Contributions and "thank you"
|
||||||
|
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
||||||
|
|
||||||
|
* Tarig0 for `Add Routine_Type to CreateStoredProc fixes #3257 (#3286)`
|
||||||
|
* oltruong for `typo fix #3025'`
|
||||||
|
* Thomas-S-B for `Removed unnecessary IErrorDetectionStrategy #749`
|
||||||
|
* Thomas-S-B for `Simplified code #750`
|
||||||
|
|
||||||
## Version 1.2.4
|
## Version 1.2.4
|
||||||
* Release date: November 6, 2018
|
* Release date: November 6, 2018
|
||||||
* Release status: General Availability
|
* Release status: General Availability
|
||||||
|
|||||||
17
README.md
17
README.md
@@ -9,12 +9,13 @@ Azure Data Studio is a data management tool that enables you to work with SQL Se
|
|||||||
|
|
||||||
Platform | Link
|
Platform | Link
|
||||||
-- | --
|
-- | --
|
||||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2038320
|
Windows User Installer | https://go.microsoft.com/fwlink/?linkid=2049972
|
||||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2038323
|
Windows System Installer | https://go.microsoft.com/fwlink/?linkid=2049975
|
||||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2038327
|
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2050146
|
||||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2038332
|
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2049981
|
||||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2038401
|
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2049986
|
||||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2038405
|
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2049989
|
||||||
|
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2050157
|
||||||
|
|
||||||
Go to our [download page](https://aka.ms/azuredatastudio) for more specific instructions.
|
Go to our [download page](https://aka.ms/azuredatastudio) for more specific instructions.
|
||||||
|
|
||||||
@@ -62,6 +63,10 @@ The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.micro
|
|||||||
## Contributions and "Thank You"
|
## Contributions and "Thank You"
|
||||||
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
||||||
|
|
||||||
|
* Tarig0 for `Add Routine_Type to CreateStoredProc fixes #3257 (#3286)`
|
||||||
|
* oltruong for `typo fix #3025'`
|
||||||
|
* Thomas-S-B for `Removed unnecessary IErrorDetectionStrategy #749`
|
||||||
|
* Thomas-S-B for `Simplified code #750`
|
||||||
* rdaniels6813 for `Add query plan theme support #3031`
|
* rdaniels6813 for `Add query plan theme support #3031`
|
||||||
* Ruturaj123 for `Fixed some typos and grammatical errors #3027`
|
* Ruturaj123 for `Fixed some typos and grammatical errors #3027`
|
||||||
* PromoFaux for `Use emoji shortcodes in CONTRIBUTING.md instead of <20> #3009`
|
* PromoFaux for `Use emoji shortcodes in CONTRIBUTING.md instead of <20> #3009`
|
||||||
|
|||||||
@@ -34,5 +34,9 @@ steps:
|
|||||||
|
|
||||||
- task: PublishTestResults@2
|
- task: PublishTestResults@2
|
||||||
inputs:
|
inputs:
|
||||||
testResultsFiles: '**/test-results.xml'
|
testResultsFiles: '**/test-results.xml'
|
||||||
condition: succeededOrFailed()
|
condition: succeededOrFailed()
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
yarn run tslint
|
||||||
|
displayName: 'Run TSLint'
|
||||||
@@ -23,4 +23,8 @@ steps:
|
|||||||
- task: PublishTestResults@2
|
- task: PublishTestResults@2
|
||||||
inputs:
|
inputs:
|
||||||
testResultsFiles: 'test-results.xml'
|
testResultsFiles: 'test-results.xml'
|
||||||
condition: succeededOrFailed()
|
condition: succeededOrFailed()
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
yarn run tslint
|
||||||
|
displayName: 'Run TSLint'
|
||||||
@@ -65,6 +65,8 @@ const excludedExtensions = [
|
|||||||
'vscode-colorize-tests',
|
'vscode-colorize-tests',
|
||||||
'ms-vscode.node-debug',
|
'ms-vscode.node-debug',
|
||||||
'ms-vscode.node-debug2',
|
'ms-vscode.node-debug2',
|
||||||
|
// {{SQL CARBON EDIT}}
|
||||||
|
'integration-tests',
|
||||||
];
|
];
|
||||||
|
|
||||||
// {{SQL CARBON EDIT}}
|
// {{SQL CARBON EDIT}}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export class JobData implements IAgentDialogData {
|
|||||||
private _operators: string[];
|
private _operators: string[];
|
||||||
private _defaultOwner: string;
|
private _defaultOwner: string;
|
||||||
private _jobCompletionActionConditions: sqlops.CategoryValue[];
|
private _jobCompletionActionConditions: sqlops.CategoryValue[];
|
||||||
|
private _jobCategoryIdsMap: sqlops.AgentJobCategory[];
|
||||||
|
|
||||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||||
public name: string;
|
public name: string;
|
||||||
@@ -46,6 +47,7 @@ export class JobData implements IAgentDialogData {
|
|||||||
public alerts: sqlops.AgentAlertInfo[];
|
public alerts: sqlops.AgentAlertInfo[];
|
||||||
public jobId: string;
|
public jobId: string;
|
||||||
public startStepId: number;
|
public startStepId: number;
|
||||||
|
public categoryType: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ownerUri: string,
|
ownerUri: string,
|
||||||
@@ -66,6 +68,8 @@ export class JobData implements IAgentDialogData {
|
|||||||
this.alerts = jobInfo.alerts;
|
this.alerts = jobInfo.alerts;
|
||||||
this.jobId = jobInfo.jobId;
|
this.jobId = jobInfo.jobId;
|
||||||
this.startStepId = jobInfo.startStepId;
|
this.startStepId = jobInfo.startStepId;
|
||||||
|
this.categoryId = jobInfo.categoryId;
|
||||||
|
this.categoryType = jobInfo.categoryType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +77,10 @@ export class JobData implements IAgentDialogData {
|
|||||||
return this._jobCategories;
|
return this._jobCategories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get jobCategoryIdsMap(): sqlops.AgentJobCategory[] {
|
||||||
|
return this._jobCategoryIdsMap;
|
||||||
|
}
|
||||||
|
|
||||||
public get operators(): string[] {
|
public get operators(): string[] {
|
||||||
return this._operators;
|
return this._operators;
|
||||||
}
|
}
|
||||||
@@ -96,7 +104,7 @@ export class JobData implements IAgentDialogData {
|
|||||||
this._jobCategories = jobDefaults.categories.map((cat) => {
|
this._jobCategories = jobDefaults.categories.map((cat) => {
|
||||||
return cat.name;
|
return cat.name;
|
||||||
});
|
});
|
||||||
|
this._jobCategoryIdsMap = jobDefaults.categories;
|
||||||
this._defaultOwner = jobDefaults.owner;
|
this._defaultOwner = jobDefaults.owner;
|
||||||
|
|
||||||
this._operators = ['', this._defaultOwner];
|
this._operators = ['', this._defaultOwner];
|
||||||
@@ -164,8 +172,8 @@ export class JobData implements IAgentDialogData {
|
|||||||
hasSchedule: false,
|
hasSchedule: false,
|
||||||
hasStep: false,
|
hasStep: false,
|
||||||
runnable: true,
|
runnable: true,
|
||||||
categoryId: 0,
|
categoryId: this.categoryId,
|
||||||
categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS
|
categoryType: this.categoryType,
|
||||||
lastRun: '',
|
lastRun: '',
|
||||||
nextRun: '',
|
nextRun: '',
|
||||||
jobId: this.jobId,
|
jobId: this.jobId,
|
||||||
|
|||||||
@@ -94,6 +94,9 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private editStepButton: sqlops.ButtonComponent;
|
private editStepButton: sqlops.ButtonComponent;
|
||||||
private deleteStepButton: sqlops.ButtonComponent;
|
private deleteStepButton: sqlops.ButtonComponent;
|
||||||
|
|
||||||
|
// Schedule tab controls
|
||||||
|
private removeScheduleButton: sqlops.ButtonComponent;
|
||||||
|
|
||||||
// Notifications tab controls
|
// Notifications tab controls
|
||||||
private notificationsTabTopLabel: sqlops.TextComponent;
|
private notificationsTabTopLabel: sqlops.TextComponent;
|
||||||
private emailCheckBox: sqlops.CheckBoxComponent;
|
private emailCheckBox: sqlops.CheckBoxComponent;
|
||||||
@@ -302,6 +305,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.stepsTable.data = this.convertStepsToData(this.steps);
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
this.steps[previousRow].id = previousStepId;
|
this.steps[previousRow].id = previousStepId;
|
||||||
this.steps[rowNumber].id = currentStepId;
|
this.steps[rowNumber].id = currentStepId;
|
||||||
|
this.stepsTable.selectedRows = [previousRow];
|
||||||
});
|
});
|
||||||
|
|
||||||
this.moveStepDownButton.onDidClick(() => {
|
this.moveStepDownButton.onDidClick(() => {
|
||||||
@@ -316,6 +320,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.stepsTable.data = this.convertStepsToData(this.steps);
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
this.steps[nextRow].id = nextStepId;
|
this.steps[nextRow].id = nextStepId;
|
||||||
this.steps[rowNumber].id = currentStepId;
|
this.steps[rowNumber].id = currentStepId;
|
||||||
|
this.stepsTable.selectedRows = [nextRow];
|
||||||
});
|
});
|
||||||
|
|
||||||
this.editStepButton.onDidClick(() => {
|
this.editStepButton.onDidClick(() => {
|
||||||
@@ -346,20 +351,30 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
if (this.stepsTable.selectedRows.length === 1) {
|
if (this.stepsTable.selectedRows.length === 1) {
|
||||||
let rowNumber = this.stepsTable.selectedRows[0];
|
let rowNumber = this.stepsTable.selectedRows[0];
|
||||||
AgentUtils.getAgentService().then((agentService) => {
|
AgentUtils.getAgentService().then((agentService) => {
|
||||||
let steps = this.model.jobSteps ? this.model.jobSteps : [];
|
let stepData = this.steps[rowNumber];
|
||||||
let stepData = this.model.jobSteps[rowNumber];
|
if (stepData.jobId) {
|
||||||
agentService.deleteJobStep(this.ownerUri, stepData).then((result) => {
|
agentService.deleteJobStep(this.ownerUri, stepData).then((result) => {
|
||||||
if (result && result.success) {
|
if (result && result.success) {
|
||||||
delete steps[rowNumber];
|
this.steps.splice(rowNumber, 1);
|
||||||
let data = this.convertStepsToData(steps);
|
let data = this.convertStepsToData(this.steps);
|
||||||
this.stepsTable.data = data;
|
this.stepsTable.data = data;
|
||||||
this.startStepDropdownValues = [];
|
this.startStepDropdownValues = [];
|
||||||
this.steps.forEach((step) => {
|
this.steps.forEach((step) => {
|
||||||
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
});
|
});
|
||||||
this.startStepDropdown.values = this.startStepDropdownValues;
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
this.steps.splice(rowNumber, 1);
|
||||||
|
let data = this.convertStepsToData(this.steps);
|
||||||
|
this.stepsTable.data = data;
|
||||||
|
this.startStepDropdownValues = [];
|
||||||
|
this.steps.forEach((step) => {
|
||||||
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
|
});
|
||||||
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -468,7 +483,11 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
label: this.PickScheduleButtonString,
|
label: this.PickScheduleButtonString,
|
||||||
width: 80
|
width: 80
|
||||||
}).component();
|
}).component();
|
||||||
this.pickScheduleButton.onDidClick((e)=>{
|
this.removeScheduleButton = view.modelBuilder.button().withProperties({
|
||||||
|
label: 'Remove schedule',
|
||||||
|
width: 100
|
||||||
|
}).component();
|
||||||
|
this.pickScheduleButton.onDidClick(()=>{
|
||||||
let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri, this.model.name);
|
let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri, this.model.name);
|
||||||
pickScheduleDialog.onSuccess((dialogModel) => {
|
pickScheduleDialog.onSuccess((dialogModel) => {
|
||||||
let selectedSchedule = dialogModel.selectedSchedule;
|
let selectedSchedule = dialogModel.selectedSchedule;
|
||||||
@@ -483,12 +502,23 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
});
|
});
|
||||||
pickScheduleDialog.showDialog();
|
pickScheduleDialog.showDialog();
|
||||||
});
|
});
|
||||||
|
this.removeScheduleButton.onDidClick(() => {
|
||||||
|
if (this.schedulesTable.selectedRows.length === 1) {
|
||||||
|
let selectedRow = this.schedulesTable.selectedRows[0];
|
||||||
|
let selectedScheduleName = this.schedulesTable.data[selectedRow][1];
|
||||||
|
for (let i = 0; i < this.schedules.length; i++) {
|
||||||
|
if (this.schedules[i].name === selectedScheduleName) {
|
||||||
|
this.schedules.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.populateScheduleTable();
|
||||||
|
}
|
||||||
|
});
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let formModel = view.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
component: this.schedulesTable,
|
component: this.schedulesTable,
|
||||||
title: this.SchedulesTopLabelString,
|
title: this.SchedulesTopLabelString,
|
||||||
actions: [this.pickScheduleButton]
|
actions: [this.pickScheduleButton, this.removeScheduleButton]
|
||||||
}]).withLayout({ width: '100%' }).component();
|
}]).withLayout({ width: '100%' }).component();
|
||||||
|
|
||||||
await view.initializeModel(formModel);
|
await view.initializeModel(formModel);
|
||||||
@@ -499,10 +529,9 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
|
|
||||||
private populateScheduleTable() {
|
private populateScheduleTable() {
|
||||||
let data = this.convertSchedulesToData(this.schedules);
|
let data = this.convertSchedulesToData(this.schedules);
|
||||||
if (data.length > 0) {
|
this.schedulesTable.data = data;
|
||||||
this.schedulesTable.data = data;
|
this.schedulesTable.height = 750;
|
||||||
this.schedulesTable.height = 750;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeNotificationsTab() {
|
private initializeNotificationsTab() {
|
||||||
@@ -674,5 +703,6 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.model.alerts = [];
|
this.model.alerts = [];
|
||||||
}
|
}
|
||||||
this.model.alerts = this.alerts;
|
this.model.alerts = this.alerts;
|
||||||
|
this.model.categoryId = +this.model.jobCategoryIdsMap.find(cat => cat.name === this.model.category).id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -29,11 +29,10 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
private readonly AdvancedTabText: string = localize('jobStepDialog.advanced', 'Advanced');
|
private readonly AdvancedTabText: string = localize('jobStepDialog.advanced', 'Advanced');
|
||||||
private readonly OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
|
private readonly OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
|
||||||
private readonly ParseCommandText: string = localize('jobStepDialog.parse','Parse');
|
private readonly ParseCommandText: string = localize('jobStepDialog.parse','Parse');
|
||||||
private readonly NextButtonText: string = localize('jobStepDialog.next', 'Next');
|
|
||||||
private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
|
|
||||||
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
|
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
|
||||||
private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
|
private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
|
||||||
private readonly BlankStepNameErrorText: string = localize('jobStepDialog.blankStepName', 'The step name cannot be left blank');
|
private readonly BlankStepNameErrorText: string = localize('jobStepDialog.blankStepName', 'The step name cannot be left blank');
|
||||||
|
private readonly ProcessExitCodeText: string = localize('jobStepDialog.processExitCode', 'Process exit code of a successful command:');
|
||||||
|
|
||||||
// General Control Titles
|
// General Control Titles
|
||||||
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
|
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
|
||||||
@@ -62,6 +61,8 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
|
|
||||||
// Dropdown options
|
// Dropdown options
|
||||||
private readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
|
private readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
|
||||||
|
private readonly Powershell: string = localize('jobStepDialog.powershell', 'PowerShell');
|
||||||
|
private readonly CmdExec: string = localize('jobStepDialog.CmdExec', 'Operating system (CmdExec)');
|
||||||
private readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
|
private readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
|
||||||
private readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
|
private readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
|
||||||
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
|
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
|
||||||
@@ -88,6 +89,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
private outputFileNameBox: sqlops.InputBoxComponent;
|
private outputFileNameBox: sqlops.InputBoxComponent;
|
||||||
private fileBrowserNameBox: sqlops.InputBoxComponent;
|
private fileBrowserNameBox: sqlops.InputBoxComponent;
|
||||||
private userInputBox: sqlops.InputBoxComponent;
|
private userInputBox: sqlops.InputBoxComponent;
|
||||||
|
private processExitCodeBox: sqlops.InputBoxComponent;
|
||||||
|
|
||||||
// Dropdowns
|
// Dropdowns
|
||||||
private typeDropdown: sqlops.DropDownComponent;
|
private typeDropdown: sqlops.DropDownComponent;
|
||||||
@@ -100,8 +102,6 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
// Buttons
|
// Buttons
|
||||||
private openButton: sqlops.ButtonComponent;
|
private openButton: sqlops.ButtonComponent;
|
||||||
private parseButton: sqlops.ButtonComponent;
|
private parseButton: sqlops.ButtonComponent;
|
||||||
private nextButton: sqlops.ButtonComponent;
|
|
||||||
private previousButton: sqlops.ButtonComponent;
|
|
||||||
private outputFileBrowserButton: sqlops.ButtonComponent;
|
private outputFileBrowserButton: sqlops.ButtonComponent;
|
||||||
|
|
||||||
// Checkbox
|
// Checkbox
|
||||||
@@ -179,18 +179,6 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
inputType: 'text'
|
inputType: 'text'
|
||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
this.nextButton = view.modelBuilder.button()
|
|
||||||
.withProperties({
|
|
||||||
label: this.NextButtonText,
|
|
||||||
enabled: false,
|
|
||||||
width: '80px'
|
|
||||||
}).component();
|
|
||||||
this.previousButton = view.modelBuilder.button()
|
|
||||||
.withProperties({
|
|
||||||
label: this.PreviousButtonText,
|
|
||||||
enabled: false,
|
|
||||||
width: '80px'
|
|
||||||
}).component();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createGeneralTab(databases: string[], queryProvider: sqlops.QueryProvider) {
|
private createGeneralTab(databases: string[], queryProvider: sqlops.QueryProvider) {
|
||||||
@@ -208,7 +196,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
this.typeDropdown = view.modelBuilder.dropDown()
|
this.typeDropdown = view.modelBuilder.dropDown()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
value: this.TSQLScript,
|
value: this.TSQLScript,
|
||||||
values: [this.TSQLScript]
|
values: [this.TSQLScript, this.CmdExec, this.Powershell]
|
||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
this.runAsDropdown = view.modelBuilder.dropDown()
|
this.runAsDropdown = view.modelBuilder.dropDown()
|
||||||
@@ -218,33 +206,20 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
})
|
})
|
||||||
.component();
|
.component();
|
||||||
this.runAsDropdown.enabled = false;
|
this.runAsDropdown.enabled = false;
|
||||||
this.typeDropdown.onValueChanged((type) => {
|
|
||||||
if (type.selected !== this.TSQLScript) {
|
|
||||||
this.runAsDropdown.value = this.AgentServiceAccount;
|
|
||||||
this.runAsDropdown.values = [this.runAsDropdown.value];
|
|
||||||
} else {
|
|
||||||
this.runAsDropdown.value = '';
|
|
||||||
this.runAsDropdown.values = [''];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.databaseDropdown = view.modelBuilder.dropDown()
|
this.databaseDropdown = view.modelBuilder.dropDown()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
value: databases[0],
|
value: databases[0],
|
||||||
values: databases
|
values: databases
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
|
this.processExitCodeBox = view.modelBuilder.inputBox()
|
||||||
|
.withProperties({
|
||||||
|
}).component();
|
||||||
|
this.processExitCodeBox.enabled = false;
|
||||||
|
|
||||||
// create the commands section
|
// create the commands section
|
||||||
this.createCommands(view, queryProvider);
|
this.createCommands(view, queryProvider);
|
||||||
|
|
||||||
let buttonContainer = view.modelBuilder.flexContainer()
|
|
||||||
.withLayout({
|
|
||||||
flexFlow: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
width: 420
|
|
||||||
}).withItems([this.openButton, this.parseButton, this.previousButton, this.nextButton], {
|
|
||||||
flex: '1 1 50%'
|
|
||||||
}).component();
|
|
||||||
|
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let formModel = view.modelBuilder.formContainer()
|
||||||
.withFormItems([{
|
.withFormItems([{
|
||||||
component: this.nameTextBox,
|
component: this.nameTextBox,
|
||||||
@@ -258,14 +233,52 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
}, {
|
}, {
|
||||||
component: this.databaseDropdown,
|
component: this.databaseDropdown,
|
||||||
title: this.DatabaseLabelString
|
title: this.DatabaseLabelString
|
||||||
|
}, {
|
||||||
|
component: this.processExitCodeBox,
|
||||||
|
title: this.ProcessExitCodeText
|
||||||
}, {
|
}, {
|
||||||
component: this.commandTextBox,
|
component: this.commandTextBox,
|
||||||
title: this.CommandLabelString,
|
title: this.CommandLabelString,
|
||||||
actions: [buttonContainer]
|
actions: [this.openButton, this.parseButton]
|
||||||
}], {
|
}], {
|
||||||
horizontal: false,
|
horizontal: false,
|
||||||
componentWidth: 420
|
componentWidth: 420
|
||||||
}).component();
|
}).component();
|
||||||
|
this.typeDropdown.onValueChanged((type) => {
|
||||||
|
switch (type.selected) {
|
||||||
|
case(this.TSQLScript):
|
||||||
|
this.runAsDropdown.value = '';
|
||||||
|
this.runAsDropdown.values = [''];
|
||||||
|
this.runAsDropdown.enabled = false;
|
||||||
|
this.databaseDropdown.enabled = true;
|
||||||
|
this.databaseDropdown.values = databases;
|
||||||
|
this.databaseDropdown.value = databases[0];
|
||||||
|
this.processExitCodeBox.value = '';
|
||||||
|
this.processExitCodeBox.enabled = false;
|
||||||
|
break;
|
||||||
|
case(this.Powershell):
|
||||||
|
this.runAsDropdown.value = this.AgentServiceAccount;
|
||||||
|
this.runAsDropdown.values = [this.runAsDropdown.value];
|
||||||
|
this.runAsDropdown.enabled = true;
|
||||||
|
this.databaseDropdown.enabled = false;
|
||||||
|
this.databaseDropdown.values = [''];
|
||||||
|
this.databaseDropdown.value = '';
|
||||||
|
this.processExitCodeBox.value = '';
|
||||||
|
this.processExitCodeBox.enabled = false;
|
||||||
|
break;
|
||||||
|
case(this.CmdExec):
|
||||||
|
this.databaseDropdown.enabled = false;
|
||||||
|
this.databaseDropdown.values = [''];
|
||||||
|
this.databaseDropdown.value = '';
|
||||||
|
this.runAsDropdown.value = this.AgentServiceAccount;
|
||||||
|
this.runAsDropdown.values = [this.runAsDropdown.value];
|
||||||
|
this.runAsDropdown.enabled = true;
|
||||||
|
this.processExitCodeBox.enabled = true;
|
||||||
|
this.processExitCodeBox.value = '0';
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||||
formWrapper.loading = false;
|
formWrapper.loading = false;
|
||||||
await view.initializeModel(formWrapper);
|
await view.initializeModel(formWrapper);
|
||||||
@@ -524,6 +537,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
this.model.outputFileName = this.outputFileNameBox.value;
|
this.model.outputFileName = this.outputFileNameBox.value;
|
||||||
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
|
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
|
||||||
this.model.command = this.commandTextBox.value ? this.commandTextBox.value : '';
|
this.model.command = this.commandTextBox.value ? this.commandTextBox.value : '';
|
||||||
|
this.model.commandExecutionSuccessCode = this.processExitCodeBox.value ? +this.processExitCodeBox.value : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async initializeDialog() {
|
public async initializeDialog() {
|
||||||
|
|||||||
@@ -17,12 +17,12 @@
|
|||||||
"configuration": [
|
"configuration": [
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "%azure.config.title%",
|
"title": "%azure.resource.config.title%",
|
||||||
"properties": {
|
"properties": {
|
||||||
"azureResource.resourceFilter": {
|
"azure.resource.config.filter": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"default": null,
|
"default": null,
|
||||||
"description": "%azure.resourceFilter.description%"
|
"description": "%azure.resource.config.filter.description%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -61,39 +61,47 @@
|
|||||||
"category": "Azure Accounts"
|
"category": "Azure Accounts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.refreshall",
|
"command": "azure.resource.refreshall",
|
||||||
"title": "%azureresource.refreshall%",
|
"title": "%azure.resource.refreshall.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/refresh_inverse.svg",
|
"dark": "resources/dark/refresh_inverse.svg",
|
||||||
"light": "resources/light/refresh.svg"
|
"light": "resources/light/refresh.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.refresh",
|
"command": "azure.resource.refresh",
|
||||||
"title": "%azureresource.refresh%",
|
"title": "%azure.resource.refresh.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/refresh_inverse.svg",
|
"dark": "resources/dark/refresh_inverse.svg",
|
||||||
"light": "resources/light/refresh.svg"
|
"light": "resources/light/refresh.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.signin",
|
"command": "azure.resource.signin",
|
||||||
"title": "%azureresource.signin%"
|
"title": "%azure.resource.signin.title%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.connectsqldb",
|
"command": "azure.resource.selectsubscriptions",
|
||||||
"title": "%azureresource.connectsqldb%",
|
"title": "%azure.resource.selectsubscriptions.title%",
|
||||||
|
"icon": {
|
||||||
|
"dark": "resources/dark/filter_inverse.svg",
|
||||||
|
"light": "resources/light/filter.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "azure.resource.connectsqlserver",
|
||||||
|
"title": "%azure.resource.connectsqlserver.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/connect_to_inverse.svg",
|
"dark": "resources/dark/connect_to_inverse.svg",
|
||||||
"light": "resources/light/connect_to.svg"
|
"light": "resources/light/connect_to.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.selectsubscriptions",
|
"command": "azure.resource.connectsqldb",
|
||||||
"title": "%azureresource.selectsubscriptions%",
|
"title": "%azure.resource.connectsqldb.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/filter_inverse.svg",
|
"dark": "resources/dark/connect_to_inverse.svg",
|
||||||
"light": "resources/light/filter.svg"
|
"light": "resources/light/connect_to.svg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -110,46 +118,47 @@
|
|||||||
"azureResource": [
|
"azureResource": [
|
||||||
{
|
{
|
||||||
"id": "azureResourceExplorer",
|
"id": "azureResourceExplorer",
|
||||||
"name": "%azure.resourceExplorer.title%"
|
"name": "%azure.resource.explorer.title%"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"menus": {
|
"menus": {
|
||||||
"view/title": [
|
"view/title": [
|
||||||
{
|
{
|
||||||
"command": "azureresource.refreshall",
|
"command": "azure.resource.refreshall",
|
||||||
"when": "view == azureResourceExplorer",
|
"when": "view == azureResourceExplorer",
|
||||||
"group": "navigation@1"
|
"group": "navigation@1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"view/item/context": [
|
"view/item/context": [
|
||||||
{
|
{
|
||||||
"command": "azureresource.connectsqldb",
|
"command": "azure.resource.selectsubscriptions",
|
||||||
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
|
"when": "viewItem == azure.resource.itemType.account",
|
||||||
"group": "1azureresource@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "azureresource.connectsqldb",
|
|
||||||
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
|
|
||||||
"group": "inline"
|
"group": "inline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.selectsubscriptions",
|
"command": "azure.resource.refresh",
|
||||||
"when": "viewItem == azureResource.itemType.account",
|
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
|
||||||
"group": "inline"
|
"group": "inline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.refresh",
|
"command": "azure.resource.connectsqlserver",
|
||||||
"when": "viewItem =~ /^azureResource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
|
"when": "viewItem == azure.resource.itemType.databaseServer",
|
||||||
|
"group": "inline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "azure.resource.connectsqldb",
|
||||||
|
"when": "viewItem == azure.resource.itemType.database",
|
||||||
"group": "inline"
|
"group": "inline"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"hasAzureResourceProviders": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"request": "2.88.0",
|
|
||||||
"azure-arm-resource": "^7.0.0",
|
"azure-arm-resource": "^7.0.0",
|
||||||
"azure-arm-sql": "^5.0.1",
|
"azure-arm-sql": "^5.0.1",
|
||||||
|
"request": "2.88.0",
|
||||||
"vscode-nls": "^4.0.0"
|
"vscode-nls": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -157,6 +166,7 @@
|
|||||||
"@types/node": "^8.0.24",
|
"@types/node": "^8.0.24",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"should": "^13.2.1",
|
"should": "^13.2.1",
|
||||||
|
"vscode": "^1.1.26",
|
||||||
"typemoq": "^2.1.0"
|
"typemoq": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,20 @@
|
|||||||
{
|
{
|
||||||
"azure.displayName": "Azure (Core)",
|
"azure.displayName": "Azure (Core)",
|
||||||
"azure.description": "Browse and work with Azure resources",
|
"azure.description": "Browse and work with Azure resources",
|
||||||
"azure.config.title": "Azure Resource Configuration",
|
|
||||||
"azure.resourceFilter.description": "The resource filter, each element is an account id, a subscription id and name separated by a slash",
|
|
||||||
"azureresource.refreshall": "Refresh All",
|
|
||||||
"azureresource.refresh": "Refresh",
|
|
||||||
"azureresource.signin": "Sign In",
|
|
||||||
"azureresource.connectsqldb": "Connect",
|
|
||||||
"azureresource.selectsubscriptions": "Select Subscriptions",
|
|
||||||
"azure.title": "Azure",
|
"azure.title": "Azure",
|
||||||
"azure.resourceExplorer.title": "Resource Explorer",
|
|
||||||
|
"azure.resource.config.title": "Azure Resource Configuration",
|
||||||
|
"azure.resource.config.filter.description": "The resource filter, each element is an account id, a subscription id and name separated by a slash",
|
||||||
|
"azure.resource.explorer.title": "Resource Explorer",
|
||||||
|
"azure.resource.refreshall.title": "Refresh All",
|
||||||
|
"azure.resource.refresh.title": "Refresh",
|
||||||
|
"azure.resource.signin.title": "Sign In",
|
||||||
|
"azure.resource.selectsubscriptions.title": "Select Subscriptions",
|
||||||
|
"azure.resource.connectsqlserver.title": "Connect",
|
||||||
|
"azure.resource.connectsqldb.title": "Connect",
|
||||||
|
|
||||||
"accounts.clearTokenCache": "Clear Azure Account Token Cache",
|
"accounts.clearTokenCache": "Clear Azure Account Token Cache",
|
||||||
|
|
||||||
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
||||||
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
||||||
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",
|
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",
|
||||||
|
|||||||
28
extensions/azurecore/src/azureResource/azure-resource.d.ts
vendored
Normal file
28
extensions/azurecore/src/azureResource/azure-resource.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { TreeDataProvider, TreeItem } from 'vscode';
|
||||||
|
import { DataProvider, Account } from 'sqlops';
|
||||||
|
|
||||||
|
export namespace azureResource {
|
||||||
|
export interface IAzureResourceProvider extends DataProvider {
|
||||||
|
getTreeDataProvider(): IAzureResourceTreeDataProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAzureResourceTreeDataProvider extends TreeDataProvider<IAzureResourceNode> {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAzureResourceNode {
|
||||||
|
readonly account: Account;
|
||||||
|
readonly subscription: AzureResourceSubscription;
|
||||||
|
readonly tenantId: string;
|
||||||
|
readonly treeItem: TreeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AzureResourceSubscription {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,35 +6,48 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { window, QuickPickItem } from 'vscode';
|
import { window, QuickPickItem } from 'vscode';
|
||||||
import * as sqlops from 'sqlops';
|
import { AzureResource } from 'sqlops';
|
||||||
import { generateGuid } from './utils';
|
import { TokenCredentials } from 'ms-rest';
|
||||||
import { ApiWrapper } from '../apiWrapper';
|
import { AppContext } from '../appContext';
|
||||||
import { TreeNode } from '../treeNodes';
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { azureResource } from './azure-resource';
|
||||||
|
import { TreeNode } from './treeNode';
|
||||||
|
import { AzureResourceCredentialError } from './errors';
|
||||||
import { AzureResourceTreeProvider } from './tree/treeProvider';
|
import { AzureResourceTreeProvider } from './tree/treeProvider';
|
||||||
import { AzureResourceDatabaseServerTreeNode } from './tree/databaseServerTreeNode';
|
|
||||||
import { AzureResourceDatabaseTreeNode } from './tree/databaseTreeNode';
|
|
||||||
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
||||||
import { AzureResourceServicePool } from './servicePool';
|
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../azureResource/interfaces';
|
||||||
import { AzureResourceSubscription } from './models';
|
import { AzureResourceServiceNames } from './constants';
|
||||||
|
|
||||||
export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: AzureResourceTreeProvider): void {
|
export function registerAzureResourceCommands(appContext: AppContext, tree: AzureResourceTreeProvider): void {
|
||||||
apiWrapper.registerCommand('azureresource.selectsubscriptions', async (node?: TreeNode) => {
|
appContext.apiWrapper.registerCommand('azure.resource.selectsubscriptions', async (node?: TreeNode) => {
|
||||||
if (!(node instanceof AzureResourceAccountTreeNode)) {
|
if (!(node instanceof AzureResourceAccountTreeNode)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subscriptionService = appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||||
|
const subscriptionFilterService = appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||||
|
|
||||||
const accountNode = node as AzureResourceAccountTreeNode;
|
const accountNode = node as AzureResourceAccountTreeNode;
|
||||||
|
|
||||||
const servicePool = AzureResourceServicePool.getInstance();
|
const subscriptions = (await accountNode.getCachedSubscriptions()) || <azureResource.AzureResourceSubscription[]>[];
|
||||||
|
if (subscriptions.length === 0) {
|
||||||
|
try {
|
||||||
|
const tokens = await this.servicePool.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
|
||||||
|
|
||||||
let subscriptions = await accountNode.getCachedSubscriptions();
|
for (const tenant of this.account.properties.tenants) {
|
||||||
if (!subscriptions || subscriptions.length === 0) {
|
const token = tokens[tenant.id].token;
|
||||||
const credentials = await servicePool.credentialService.getCredentials(accountNode.account, sqlops.AzureResource.ResourceManagement);
|
const tokenType = tokens[tenant.id].tokenType;
|
||||||
subscriptions = await servicePool.subscriptionService.getSubscriptions(accountNode.account, credentials);
|
|
||||||
|
subscriptions.push(...await subscriptionService.getSubscriptions(accountNode.account, new TokenCredentials(token, tokenType)));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new AzureResourceCredentialError(localize('azure.resource.selectsubscriptions.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', this.account.key.accountId), error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedSubscriptions = (await servicePool.subscriptionFilterService.getSelectedSubscriptions(accountNode.account)) || <AzureResourceSubscription[]>[];
|
let selectedSubscriptions = (await subscriptionFilterService.getSelectedSubscriptions(accountNode.account)) || <azureResource.AzureResourceSubscription[]>[];
|
||||||
const selectedSubscriptionIds: string[] = [];
|
const selectedSubscriptionIds: string[] = [];
|
||||||
if (selectedSubscriptions.length > 0) {
|
if (selectedSubscriptions.length > 0) {
|
||||||
selectedSubscriptionIds.push(...selectedSubscriptions.map((subscription) => subscription.id));
|
selectedSubscriptionIds.push(...selectedSubscriptions.map((subscription) => subscription.id));
|
||||||
@@ -43,11 +56,11 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
|
|||||||
selectedSubscriptionIds.push(...subscriptions.map((subscription) => subscription.id));
|
selectedSubscriptionIds.push(...subscriptions.map((subscription) => subscription.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubscriptionQuickPickItem extends QuickPickItem {
|
interface AzureResourceSubscriptionQuickPickItem extends QuickPickItem {
|
||||||
subscription: AzureResourceSubscription;
|
subscription: azureResource.AzureResourceSubscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subscriptionItems: SubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
const subscriptionQuickPickItems: AzureResourceSubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
||||||
return {
|
return {
|
||||||
label: subscription.name,
|
label: subscription.name,
|
||||||
picked: selectedSubscriptionIds.indexOf(subscription.id) !== -1,
|
picked: selectedSubscriptionIds.indexOf(subscription.id) !== -1,
|
||||||
@@ -55,66 +68,22 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const pickedSubscriptionItems = (await window.showQuickPick(subscriptionItems, { canPickMany: true }));
|
const selectedSubscriptionQuickPickItems = (await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true }));
|
||||||
if (pickedSubscriptionItems && pickedSubscriptionItems.length > 0) {
|
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
|
||||||
tree.refresh(node, false);
|
tree.refresh(node, false);
|
||||||
|
|
||||||
const pickedSubscriptions = pickedSubscriptionItems.map((subscriptionItem) => subscriptionItem.subscription);
|
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
|
||||||
await servicePool.subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, pickedSubscriptions);
|
await subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, selectedSubscriptions);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
apiWrapper.registerCommand('azureresource.refreshall', () => tree.notifyNodeChanged(undefined));
|
appContext.apiWrapper.registerCommand('azure.resource.refreshall', () => tree.notifyNodeChanged(undefined));
|
||||||
|
|
||||||
apiWrapper.registerCommand('azureresource.refresh', async (node?: TreeNode) => {
|
appContext.apiWrapper.registerCommand('azure.resource.refresh', async (node?: TreeNode) => {
|
||||||
tree.refresh(node, true);
|
tree.refresh(node, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
apiWrapper.registerCommand('azureresource.connectsqldb', async (node?: TreeNode) => {
|
appContext.apiWrapper.registerCommand('azure.resource.signin', async (node?: TreeNode) => {
|
||||||
let connectionProfile: sqlops.IConnectionProfile = {
|
appContext.apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
|
||||||
id: generateGuid(),
|
|
||||||
connectionName: undefined,
|
|
||||||
serverName: undefined,
|
|
||||||
databaseName: undefined,
|
|
||||||
userName: undefined,
|
|
||||||
password: '',
|
|
||||||
authenticationType: undefined,
|
|
||||||
savePassword: true,
|
|
||||||
groupFullName: '',
|
|
||||||
groupId: '',
|
|
||||||
providerName: undefined,
|
|
||||||
saveProfile: true,
|
|
||||||
options: {
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (node instanceof AzureResourceDatabaseServerTreeNode) {
|
|
||||||
let databaseServer = node.databaseServer;
|
|
||||||
connectionProfile.connectionName = `connection to '${databaseServer.defaultDatabaseName}' on '${databaseServer.fullName}'`;
|
|
||||||
connectionProfile.serverName = databaseServer.fullName;
|
|
||||||
connectionProfile.databaseName = databaseServer.defaultDatabaseName;
|
|
||||||
connectionProfile.userName = databaseServer.loginName;
|
|
||||||
connectionProfile.authenticationType = 'SqlLogin';
|
|
||||||
connectionProfile.providerName = 'MSSQL';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node instanceof AzureResourceDatabaseTreeNode) {
|
|
||||||
let database = node.database;
|
|
||||||
connectionProfile.connectionName = `connection to '${database.name}' on '${database.serverFullName}'`;
|
|
||||||
connectionProfile.serverName = database.serverFullName;
|
|
||||||
connectionProfile.databaseName = database.name;
|
|
||||||
connectionProfile.userName = database.loginName;
|
|
||||||
connectionProfile.authenticationType = 'SqlLogin';
|
|
||||||
connectionProfile.providerName = 'MSSQL';
|
|
||||||
}
|
|
||||||
|
|
||||||
const conn = await apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
|
|
||||||
if (conn) {
|
|
||||||
apiWrapper.executeCommand('workbench.view.connections');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
apiWrapper.registerCommand('azureresource.signin', async (node?: TreeNode) => {
|
|
||||||
apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,19 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export enum AzureResourceItemType {
|
export enum AzureResourceItemType {
|
||||||
account = 'azureResource.itemType.account',
|
account = 'azure.resource.itemType.account',
|
||||||
subscription = 'azureResource.itemType.subscription',
|
subscription = 'azure.resource.itemType.subscription',
|
||||||
databaseContainer = 'azureResource.itemType.databaseContainer',
|
databaseContainer = 'azure.resource.itemType.databaseContainer',
|
||||||
database = 'azureResource.itemType.database',
|
database = 'azure.resource.itemType.database',
|
||||||
databaseServerContainer = 'azureResource.itemType.databaseServerContainer',
|
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
|
||||||
databaseServer = 'azureResource.itemType.databaseServer',
|
databaseServer = 'azure.resource.itemType.databaseServer',
|
||||||
message = 'azureResource.itemType.message'
|
message = 'azure.resource.itemType.message'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum AzureResourceServiceNames {
|
||||||
|
cacheService = 'AzureResourceCacheService',
|
||||||
|
accountService = 'AzureResourceAccountService',
|
||||||
|
subscriptionService = 'AzureResourceSubscriptionService',
|
||||||
|
subscriptionFilterService = 'AzureResourceSubscriptionFilterService',
|
||||||
|
tenantService = 'AzureResourceTenantService'
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
export class AzureResourceCredentialError extends Error {
|
export class AzureResourceCredentialError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
public innerError: Error
|
public readonly innerError: Error
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,49 +6,40 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
import * as sqlops from 'sqlops';
|
import { Account, DidChangeAccountsParams } from 'sqlops';
|
||||||
import { Event } from 'vscode';
|
import { Event } from 'vscode';
|
||||||
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabaseServer, AzureResourceDatabase } from './models';
|
import { azureResource } from './azure-resource';
|
||||||
|
|
||||||
export interface IAzureResourceAccountService {
|
export interface IAzureResourceAccountService {
|
||||||
getAccounts(): Promise<sqlops.Account[]>;
|
getAccounts(): Promise<Account[]>;
|
||||||
|
|
||||||
readonly onDidChangeAccounts: Event<sqlops.DidChangeAccountsParams>;
|
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAzureResourceCredentialService {
|
|
||||||
getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceSubscriptionService {
|
export interface IAzureResourceSubscriptionService {
|
||||||
getSubscriptions(account: sqlops.Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]>;
|
getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceSubscriptionFilterService {
|
export interface IAzureResourceSubscriptionFilterService {
|
||||||
getSelectedSubscriptions(account: sqlops.Account): Promise<AzureResourceSubscription[]>;
|
getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]>;
|
||||||
|
|
||||||
saveSelectedSubscriptions(account: sqlops.Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void>;
|
saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAzureResourceDatabaseServerService {
|
|
||||||
getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAzureResourceDatabaseService {
|
|
||||||
getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceCacheService {
|
export interface IAzureResourceCacheService {
|
||||||
|
generateKey(id: string): string;
|
||||||
|
|
||||||
get<T>(key: string): T | undefined;
|
get<T>(key: string): T | undefined;
|
||||||
|
|
||||||
update<T>(key: string, value: T): void;
|
update<T>(key: string, value: T): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceContextService {
|
export interface IAzureResourceTenantService {
|
||||||
getAbsolutePath(relativePath: string): string;
|
getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string>;
|
||||||
|
|
||||||
executeCommand(commandId: string, ...args: any[]): void;
|
|
||||||
|
|
||||||
showErrorMessage(errorMessage: string): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAzureResourceNodeWithProviderId {
|
||||||
|
resourceProviderId: string;
|
||||||
|
resourceNode: azureResource.IAzureResourceNode;
|
||||||
|
}
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { NodeInfo } from 'sqlops';
|
import { NodeInfo } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { TreeNode } from './treeNode';
|
||||||
|
import { AzureResourceItemType } from './constants';
|
||||||
|
|
||||||
export class AzureResourceMessageTreeNode extends TreeNode {
|
export class AzureResourceMessageTreeNode extends TreeNode {
|
||||||
public constructor(
|
public constructor(
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { IConnectionProfile } from 'sqlops';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
|
||||||
|
import { TreeNode } from '../../treeNode';
|
||||||
|
import { generateGuid } from '../../utils';
|
||||||
|
import { AzureResourceItemType } from '../../constants';
|
||||||
|
import { IAzureResourceDatabaseNode } from './interfaces';
|
||||||
|
import { AzureResourceResourceTreeNode } from '../../resourceTreeNode';
|
||||||
|
|
||||||
|
export function registerAzureResourceDatabaseCommands(appContext: AppContext): void {
|
||||||
|
appContext.apiWrapper.registerCommand('azure.resource.connectsqldb', async (node?: TreeNode) => {
|
||||||
|
if (!node)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeItem = await node.getTreeItem();
|
||||||
|
if (treeItem.contextValue !== AzureResourceItemType.database) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode;
|
||||||
|
const database = (resourceNode as IAzureResourceDatabaseNode).database;
|
||||||
|
|
||||||
|
let connectionProfile: IConnectionProfile = {
|
||||||
|
id: generateGuid(),
|
||||||
|
connectionName: undefined,
|
||||||
|
serverName: database.serverFullName,
|
||||||
|
databaseName: database.name,
|
||||||
|
userName: database.loginName,
|
||||||
|
password: '',
|
||||||
|
authenticationType: 'SqlLogin',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: '',
|
||||||
|
groupId: '',
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
saveProfile: true,
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
|
||||||
|
if (conn) {
|
||||||
|
appContext.apiWrapper.executeCommand('workbench.view.connections');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ExtensionContext } from 'vscode';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseService } from './interfaces';
|
||||||
|
import { AzureResourceDatabaseTreeDataProvider } from './databaseTreeDataProvider';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseProvider implements azureResource.IAzureResourceProvider {
|
||||||
|
public constructor(
|
||||||
|
databaseService: IAzureResourceDatabaseService,
|
||||||
|
apiWrapper: ApiWrapper,
|
||||||
|
extensionContext: ExtensionContext
|
||||||
|
) {
|
||||||
|
this._databaseService = databaseService;
|
||||||
|
this._apiWrapper = apiWrapper;
|
||||||
|
this._extensionContext = extensionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
|
||||||
|
return new AzureResourceDatabaseTreeDataProvider(this._databaseService, this._apiWrapper, this._extensionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get providerId(): string {
|
||||||
|
return 'azure.resource.providers.database';
|
||||||
|
}
|
||||||
|
|
||||||
|
private _databaseService: IAzureResourceDatabaseService = undefined;
|
||||||
|
private _apiWrapper: ApiWrapper = undefined;
|
||||||
|
private _extensionContext: ExtensionContext = undefined;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
|
import { SqlManagementClient } from 'azure-arm-sql';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseService } from './interfaces';
|
||||||
|
import { AzureResourceDatabase } from './models';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseService implements IAzureResourceDatabaseService {
|
||||||
|
public async getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabase[]> {
|
||||||
|
const databases: AzureResourceDatabase[] = [];
|
||||||
|
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
|
||||||
|
const svrs = await sqlManagementClient.servers.list();
|
||||||
|
for (const svr of svrs) {
|
||||||
|
// Extract resource group name from svr.id
|
||||||
|
const svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
|
||||||
|
if (!svrIdRegExp.test(svr.id)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const founds = svrIdRegExp.exec(svr.id);
|
||||||
|
const resouceGroup = founds[1];
|
||||||
|
|
||||||
|
const dbs = await sqlManagementClient.databases.listByServer(resouceGroup, svr.name);
|
||||||
|
dbs.forEach((db) => databases.push({
|
||||||
|
name: db.name,
|
||||||
|
serverName: svr.name,
|
||||||
|
serverFullName: svr.fullyQualifiedDomainName,
|
||||||
|
loginName: svr.administratorLogin
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return databases;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { AzureResource } from 'sqlops';
|
||||||
|
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
|
||||||
|
import { TokenCredentials } from 'ms-rest';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseService, IAzureResourceDatabaseNode } from './interfaces';
|
||||||
|
import { AzureResourceDatabase } from './models';
|
||||||
|
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider {
|
||||||
|
public constructor(
|
||||||
|
databaseService: IAzureResourceDatabaseService,
|
||||||
|
apiWrapper: ApiWrapper,
|
||||||
|
extensionContext: ExtensionContext
|
||||||
|
) {
|
||||||
|
this._databaseService = databaseService;
|
||||||
|
this._apiWrapper = apiWrapper;
|
||||||
|
this._extensionContext = extensionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable<TreeItem> {
|
||||||
|
return element.treeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChildren(element?: azureResource.IAzureResourceNode): Promise<azureResource.IAzureResourceNode[]> {
|
||||||
|
if (!element) {
|
||||||
|
return [this.createContainerNode()];
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
|
||||||
|
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
|
||||||
|
|
||||||
|
const databases: AzureResourceDatabase[] = (await this._databaseService.getDatabases(element.subscription, credential)) || <AzureResourceDatabase[]>[];
|
||||||
|
|
||||||
|
return databases.map((database) => <IAzureResourceDatabaseNode>{
|
||||||
|
account: element.account,
|
||||||
|
subscription: element.subscription,
|
||||||
|
tenantId: element.tenantId,
|
||||||
|
database: database,
|
||||||
|
treeItem: {
|
||||||
|
id: `databaseServer_${database.serverFullName}.database_${database.name}`,
|
||||||
|
label: `${database.name} (${database.serverName})`,
|
||||||
|
iconPath: {
|
||||||
|
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'),
|
||||||
|
light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg')
|
||||||
|
},
|
||||||
|
collapsibleState: TreeItemCollapsibleState.None,
|
||||||
|
contextValue: AzureResourceItemType.database
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createContainerNode(): azureResource.IAzureResourceNode {
|
||||||
|
return {
|
||||||
|
account: undefined,
|
||||||
|
subscription: undefined,
|
||||||
|
tenantId: undefined,
|
||||||
|
treeItem: {
|
||||||
|
id: AzureResourceDatabaseTreeDataProvider.containerId,
|
||||||
|
label: AzureResourceDatabaseTreeDataProvider.containerLabel,
|
||||||
|
iconPath: {
|
||||||
|
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||||
|
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||||
|
},
|
||||||
|
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: AzureResourceItemType.databaseContainer
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _databaseService: IAzureResourceDatabaseService = undefined;
|
||||||
|
private _apiWrapper: ApiWrapper = undefined;
|
||||||
|
private _extensionContext: ExtensionContext = undefined;
|
||||||
|
|
||||||
|
private static readonly containerId = 'azure.resource.providers.database.treeDataProvider.databaseContainer';
|
||||||
|
private static readonly containerLabel = localize('azure.resource.providers.database.treeDataProvider.databaseContainerLabel', 'SQL Databases');
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { AzureResourceDatabase } from './models';
|
||||||
|
|
||||||
|
export interface IAzureResourceDatabaseService {
|
||||||
|
getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabase[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAzureResourceDatabaseNode extends azureResource.IAzureResourceNode {
|
||||||
|
readonly database: AzureResourceDatabase;
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/*---------------------------------------------------------------------------------------------
|
/*---------------------------------------------------------------------------------------------
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
@@ -6,11 +5,9 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// TODO: The content of this file should be refactored to an extension
|
export interface AzureResourceDatabase {
|
||||||
export function getKnoxUrl(host: string, port: string): string {
|
name: string;
|
||||||
return `https://${host}:${port}/gateway`;
|
serverName: string;
|
||||||
}
|
serverFullName: string;
|
||||||
|
loginName: string;
|
||||||
export function getLivyUrl(serverName: string, port: string): string {
|
|
||||||
return getKnoxUrl(serverName, port) + '/default/livy/v1/';
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { IConnectionProfile } from 'sqlops';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
|
||||||
|
import { TreeNode } from '../../treeNode';
|
||||||
|
import { generateGuid } from '../../utils';
|
||||||
|
import { AzureResourceItemType } from '../../constants';
|
||||||
|
import { IAzureResourceDatabaseServerNode } from './interfaces';
|
||||||
|
import { AzureResourceResourceTreeNode } from '../../resourceTreeNode';
|
||||||
|
|
||||||
|
export function registerAzureResourceDatabaseServerCommands(appContext: AppContext): void {
|
||||||
|
appContext.apiWrapper.registerCommand('azure.resource.connectsqlserver', async (node?: TreeNode) => {
|
||||||
|
if (!node)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeItem = await node.getTreeItem();
|
||||||
|
if (treeItem.contextValue !== AzureResourceItemType.databaseServer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode;
|
||||||
|
const databaseServer = (resourceNode as IAzureResourceDatabaseServerNode).databaseServer;
|
||||||
|
|
||||||
|
let connectionProfile: IConnectionProfile = {
|
||||||
|
id: generateGuid(),
|
||||||
|
connectionName: undefined,
|
||||||
|
serverName: databaseServer.fullName,
|
||||||
|
databaseName: databaseServer.defaultDatabaseName,
|
||||||
|
userName: databaseServer.loginName,
|
||||||
|
password: '',
|
||||||
|
authenticationType: 'SqlLogin',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: '',
|
||||||
|
groupId: '',
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
saveProfile: true,
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
|
||||||
|
if (conn) {
|
||||||
|
appContext.apiWrapper.executeCommand('workbench.view.connections');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ExtensionContext } from 'vscode';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseServerService } from './interfaces';
|
||||||
|
import { AzureResourceDatabaseServerTreeDataProvider } from './databaseServerTreeDataProvider';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseServerProvider implements azureResource.IAzureResourceProvider {
|
||||||
|
public constructor(
|
||||||
|
databaseServerService: IAzureResourceDatabaseServerService,
|
||||||
|
apiWrapper: ApiWrapper,
|
||||||
|
extensionContext: ExtensionContext
|
||||||
|
) {
|
||||||
|
this._databaseServerService = databaseServerService;
|
||||||
|
this._apiWrapper = apiWrapper;
|
||||||
|
this._extensionContext = extensionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
|
||||||
|
return new AzureResourceDatabaseServerTreeDataProvider(this._databaseServerService, this._apiWrapper, this._extensionContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get providerId(): string {
|
||||||
|
return 'azure.resource.providers.databaseServer';
|
||||||
|
}
|
||||||
|
|
||||||
|
private _databaseServerService: IAzureResourceDatabaseServerService = undefined;
|
||||||
|
private _apiWrapper: ApiWrapper = undefined;
|
||||||
|
private _extensionContext: ExtensionContext = undefined;
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
|
import { SqlManagementClient } from 'azure-arm-sql';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseServerService } from './interfaces';
|
||||||
|
import { AzureResourceDatabaseServer } from './models';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService {
|
||||||
|
public async getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabaseServer[]> {
|
||||||
|
const databaseServers: AzureResourceDatabaseServer[] = [];
|
||||||
|
|
||||||
|
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
|
||||||
|
const svrs = await sqlManagementClient.servers.list();
|
||||||
|
|
||||||
|
svrs.forEach((svr) => databaseServers.push({
|
||||||
|
name: svr.name,
|
||||||
|
fullName: svr.fullyQualifiedDomainName,
|
||||||
|
loginName: svr.administratorLogin,
|
||||||
|
defaultDatabaseName: 'master'
|
||||||
|
}));
|
||||||
|
|
||||||
|
return databaseServers;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { AzureResource } from 'sqlops';
|
||||||
|
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
|
||||||
|
import { TokenCredentials } from 'ms-rest';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { IAzureResourceDatabaseServerService, IAzureResourceDatabaseServerNode } from './interfaces';
|
||||||
|
import { AzureResourceDatabaseServer } from './models';
|
||||||
|
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
|
export class AzureResourceDatabaseServerTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider {
|
||||||
|
public constructor(
|
||||||
|
databaseServerService: IAzureResourceDatabaseServerService,
|
||||||
|
apiWrapper: ApiWrapper,
|
||||||
|
extensionContext: ExtensionContext
|
||||||
|
) {
|
||||||
|
this._databaseServerService = databaseServerService;
|
||||||
|
this._apiWrapper = apiWrapper;
|
||||||
|
this._extensionContext = extensionContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable<TreeItem> {
|
||||||
|
return element.treeItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChildren(element?: azureResource.IAzureResourceNode): Promise<azureResource.IAzureResourceNode[]> {
|
||||||
|
if (!element) {
|
||||||
|
return [this.createContainerNode()];
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
|
||||||
|
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
|
||||||
|
|
||||||
|
const databaseServers: AzureResourceDatabaseServer[] = (await this._databaseServerService.getDatabaseServers(element.subscription, credential)) || <AzureResourceDatabaseServer[]>[];
|
||||||
|
|
||||||
|
return databaseServers.map((databaseServer) => <IAzureResourceDatabaseServerNode>{
|
||||||
|
account: element.account,
|
||||||
|
subscription: element.subscription,
|
||||||
|
tenantId: element.tenantId,
|
||||||
|
databaseServer: databaseServer,
|
||||||
|
treeItem: {
|
||||||
|
id: `databaseServer_${databaseServer.name}`,
|
||||||
|
label: databaseServer.name,
|
||||||
|
iconPath: {
|
||||||
|
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||||
|
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
|
||||||
|
},
|
||||||
|
collapsibleState: TreeItemCollapsibleState.None,
|
||||||
|
contextValue: AzureResourceItemType.databaseServer
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createContainerNode(): azureResource.IAzureResourceNode {
|
||||||
|
return {
|
||||||
|
account: undefined,
|
||||||
|
subscription: undefined,
|
||||||
|
tenantId: undefined,
|
||||||
|
treeItem: {
|
||||||
|
id: AzureResourceDatabaseServerTreeDataProvider.containerId,
|
||||||
|
label: AzureResourceDatabaseServerTreeDataProvider.containerLabel,
|
||||||
|
iconPath: {
|
||||||
|
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||||
|
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||||
|
},
|
||||||
|
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: AzureResourceItemType.databaseServerContainer
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private _databaseServerService: IAzureResourceDatabaseServerService = undefined;
|
||||||
|
private _apiWrapper: ApiWrapper = undefined;
|
||||||
|
private _extensionContext: ExtensionContext = undefined;
|
||||||
|
|
||||||
|
private static readonly containerId = 'azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer';
|
||||||
|
private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainerLabel', 'SQL Servers');
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azure-resource';
|
||||||
|
import { AzureResourceDatabaseServer } from './models';
|
||||||
|
|
||||||
|
export interface IAzureResourceDatabaseServerService {
|
||||||
|
getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credentials: ServiceClientCredentials): Promise<AzureResourceDatabaseServer[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAzureResourceDatabaseServerNode extends azureResource.IAzureResourceNode {
|
||||||
|
readonly databaseServer: AzureResourceDatabaseServer;
|
||||||
|
}
|
||||||
@@ -5,21 +5,9 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export interface AzureResourceSubscription {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AzureResourceDatabaseServer {
|
export interface AzureResourceDatabaseServer {
|
||||||
name: string;
|
name: string;
|
||||||
fullName: string;
|
fullName: string;
|
||||||
loginName: string;
|
loginName: string;
|
||||||
defaultDatabaseName: string;
|
defaultDatabaseName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AzureResourceDatabase {
|
|
||||||
name: string;
|
|
||||||
serverName: string;
|
|
||||||
serverFullName: string;
|
|
||||||
loginName: string;
|
|
||||||
}
|
|
||||||
129
extensions/azurecore/src/azureResource/resourceService.ts
Normal file
129
extensions/azurecore/src/azureResource/resourceService.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { extensions, TreeItem } from 'vscode';
|
||||||
|
import { Account } from 'sqlops';
|
||||||
|
|
||||||
|
import { azureResource } from './azure-resource';
|
||||||
|
import { IAzureResourceNodeWithProviderId } from './interfaces';
|
||||||
|
|
||||||
|
export class AzureResourceService {
|
||||||
|
private constructor() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): AzureResourceService {
|
||||||
|
return AzureResourceService._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async listResourceProviderIds(): Promise<string[]> {
|
||||||
|
await this.ensureResourceProvidersRegistered();
|
||||||
|
|
||||||
|
return Object.keys(this._resourceProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerResourceProvider(resourceProvider: azureResource.IAzureResourceProvider): void {
|
||||||
|
this.doRegisterResourceProvider(resourceProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearResourceProviders(): void {
|
||||||
|
this._resourceProviders = {};
|
||||||
|
this._treeDataProviders = {};
|
||||||
|
this._areResourceProvidersLoaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getRootChildren(resourceProviderId: string, account: Account, subscription: azureResource.AzureResourceSubscription, tenatId: string): Promise<IAzureResourceNodeWithProviderId[]> {
|
||||||
|
await this.ensureResourceProvidersRegistered();
|
||||||
|
|
||||||
|
if (!(resourceProviderId in this._resourceProviders)) {
|
||||||
|
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeDataProvider = this._treeDataProviders[resourceProviderId];
|
||||||
|
const children = await treeDataProvider.getChildren();
|
||||||
|
|
||||||
|
return children.map((child) => <IAzureResourceNodeWithProviderId>{
|
||||||
|
resourceProviderId: resourceProviderId,
|
||||||
|
resourceNode: <azureResource.IAzureResourceNode>{
|
||||||
|
account: account,
|
||||||
|
subscription: subscription,
|
||||||
|
tenantId: tenatId,
|
||||||
|
treeItem: child.treeItem
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChildren(resourceProviderId: string, element: azureResource.IAzureResourceNode): Promise<IAzureResourceNodeWithProviderId[]> {
|
||||||
|
await this.ensureResourceProvidersRegistered();
|
||||||
|
|
||||||
|
if (!(resourceProviderId in this._resourceProviders)) {
|
||||||
|
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeDataProvider = this._treeDataProviders[resourceProviderId];
|
||||||
|
const children = await treeDataProvider.getChildren(element);
|
||||||
|
|
||||||
|
return children.map((child) => <IAzureResourceNodeWithProviderId>{
|
||||||
|
resourceProviderId: resourceProviderId,
|
||||||
|
resourceNode: child
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getTreeItem(resourceProviderId: string, element?: azureResource.IAzureResourceNode): Promise<TreeItem> {
|
||||||
|
await this.ensureResourceProvidersRegistered();
|
||||||
|
|
||||||
|
if (!(resourceProviderId in this._resourceProviders)) {
|
||||||
|
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const treeDataProvider = this._treeDataProviders[resourceProviderId];
|
||||||
|
return treeDataProvider.getTreeItem(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get areResourceProvidersLoaded(): boolean {
|
||||||
|
return this._areResourceProvidersLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set areResourceProvidersLoaded(value: boolean) {
|
||||||
|
this._areResourceProvidersLoaded = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureResourceProvidersRegistered(): Promise<void> {
|
||||||
|
if (this._areResourceProvidersLoaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const extension of extensions.all) {
|
||||||
|
const contributes = extension.packageJSON && extension.packageJSON.contributes;
|
||||||
|
if (!contributes) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contributes['hasAzureResourceProviders']) {
|
||||||
|
await extension.activate();
|
||||||
|
|
||||||
|
if (extension.exports && extension.exports.provideResources) {
|
||||||
|
for (const resourceProvider of <azureResource.IAzureResourceProvider[]>extension.exports.provideResources()) {
|
||||||
|
this.doRegisterResourceProvider(resourceProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._areResourceProvidersLoaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private doRegisterResourceProvider(resourceProvider: azureResource.IAzureResourceProvider): void {
|
||||||
|
this._resourceProviders[resourceProvider.providerId] = resourceProvider;
|
||||||
|
this._treeDataProviders[resourceProvider.providerId] = resourceProvider.getTreeDataProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _areResourceProvidersLoaded: boolean = false;
|
||||||
|
private _resourceProviders: { [resourceProviderId: string]: azureResource.IAzureResourceProvider } = {};
|
||||||
|
private _treeDataProviders: { [resourceProviderId: string]: azureResource.IAzureResourceTreeDataProvider } = {};
|
||||||
|
|
||||||
|
private static readonly _instance = new AzureResourceService();
|
||||||
|
}
|
||||||
79
extensions/azurecore/src/azureResource/resourceTreeNode.ts
Normal file
79
extensions/azurecore/src/azureResource/resourceTreeNode.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { NodeInfo } from 'sqlops';
|
||||||
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { TreeNode } from './treeNode';
|
||||||
|
import { AzureResourceService } from './resourceService';
|
||||||
|
import { IAzureResourceNodeWithProviderId } from './interfaces';
|
||||||
|
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
||||||
|
import { AzureResourceErrorMessageUtil } from './utils';
|
||||||
|
|
||||||
|
export class AzureResourceResourceTreeNode extends TreeNode {
|
||||||
|
public constructor(
|
||||||
|
public readonly resourceNodeWithProviderId: IAzureResourceNodeWithProviderId,
|
||||||
|
parent: TreeNode
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getChildren(): Promise<TreeNode[]> {
|
||||||
|
// It is a leaf node.
|
||||||
|
if (this.resourceNodeWithProviderId.resourceNode.treeItem.collapsibleState === TreeItemCollapsibleState.None) {
|
||||||
|
return <TreeNode[]>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const children = await this._resourceService.getChildren(this.resourceNodeWithProviderId.resourceProviderId, this.resourceNodeWithProviderId.resourceNode);
|
||||||
|
|
||||||
|
if (children.length === 0) {
|
||||||
|
return [AzureResourceMessageTreeNode.create(AzureResourceResourceTreeNode.noResourcesLabel, this)];
|
||||||
|
} else {
|
||||||
|
return children.map((child) => {
|
||||||
|
// To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered'
|
||||||
|
child.resourceNode.treeItem.id = `${this.resourceNodeWithProviderId.resourceNode.treeItem.id}.${child.resourceNode.treeItem.id}`;
|
||||||
|
return new AzureResourceResourceTreeNode(child, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||||
|
return this._resourceService.getTreeItem(this.resourceNodeWithProviderId.resourceProviderId, this.resourceNodeWithProviderId.resourceNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNodeInfo(): NodeInfo {
|
||||||
|
const treeItem = this.resourceNodeWithProviderId.resourceNode.treeItem;
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: treeItem.label,
|
||||||
|
isLeaf: treeItem.collapsibleState === TreeItemCollapsibleState.None ? true : false,
|
||||||
|
errorMessage: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
nodePath: this.generateNodePath(),
|
||||||
|
nodeStatus: undefined,
|
||||||
|
nodeType: treeItem.contextValue,
|
||||||
|
nodeSubType: undefined,
|
||||||
|
iconType: treeItem.contextValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get nodePathValue(): string {
|
||||||
|
return this.resourceNodeWithProviderId.resourceNode.treeItem.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _resourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
|
private static readonly noResourcesLabel = localize('azure.resource.resourceTreeNode.noResourcesLabel', 'No Resources found.');
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import {
|
|
||||||
IAzureResourceAccountService,
|
|
||||||
IAzureResourceCredentialService,
|
|
||||||
IAzureResourceSubscriptionService,
|
|
||||||
IAzureResourceSubscriptionFilterService,
|
|
||||||
IAzureResourceDatabaseService,
|
|
||||||
IAzureResourceDatabaseServerService,
|
|
||||||
IAzureResourceCacheService,
|
|
||||||
IAzureResourceContextService } from './interfaces';
|
|
||||||
|
|
||||||
export class AzureResourceServicePool {
|
|
||||||
private constructor() { }
|
|
||||||
|
|
||||||
public static getInstance(): AzureResourceServicePool {
|
|
||||||
return AzureResourceServicePool._instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public contextService: IAzureResourceContextService;
|
|
||||||
public cacheService: IAzureResourceCacheService;
|
|
||||||
public accountService: IAzureResourceAccountService;
|
|
||||||
public credentialService: IAzureResourceCredentialService;
|
|
||||||
public subscriptionService: IAzureResourceSubscriptionService;
|
|
||||||
public subscriptionFilterService: IAzureResourceSubscriptionFilterService;
|
|
||||||
public databaseService: IAzureResourceDatabaseService;
|
|
||||||
public databaseServerService: IAzureResourceDatabaseServerService;
|
|
||||||
|
|
||||||
private static readonly _instance = new AzureResourceServicePool();
|
|
||||||
}
|
|
||||||
@@ -5,21 +5,30 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ExtensionContext } from "vscode";
|
import { ExtensionContext } from 'vscode';
|
||||||
|
|
||||||
import { IAzureResourceCacheService } from "../interfaces";
|
import { IAzureResourceCacheService } from '../interfaces';
|
||||||
|
|
||||||
export class AzureResourceCacheService implements IAzureResourceCacheService {
|
export class AzureResourceCacheService implements IAzureResourceCacheService {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly context: ExtensionContext
|
context: ExtensionContext
|
||||||
) {
|
) {
|
||||||
|
this._context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get<T>(key: string): T | undefined {
|
public generateKey(id: string): string {
|
||||||
return this.context.workspaceState.get(key);
|
return `${AzureResourceCacheService.cacheKeyPrefix}.${id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get<T>(key: string): T | undefined {
|
||||||
|
return this._context.workspaceState.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public update<T>(key: string, value: T): void {
|
public update<T>(key: string, value: T): void {
|
||||||
this.context.workspaceState.update(key, value);
|
this._context.workspaceState.update(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _context: ExtensionContext = undefined;
|
||||||
|
|
||||||
|
private static readonly cacheKeyPrefix = 'azure.resource.cache';
|
||||||
}
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { ExtensionContext } from "vscode";
|
|
||||||
import { ApiWrapper } from "../../apiWrapper";
|
|
||||||
|
|
||||||
import { IAzureResourceContextService } from "../interfaces";
|
|
||||||
|
|
||||||
export class AzureResourceContextService implements IAzureResourceContextService {
|
|
||||||
public constructor(
|
|
||||||
context: ExtensionContext,
|
|
||||||
apiWrapper: ApiWrapper
|
|
||||||
) {
|
|
||||||
this._context = context;
|
|
||||||
this._apiWrapper = apiWrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getAbsolutePath(relativePath: string): string {
|
|
||||||
return this._context.asAbsolutePath(relativePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
public executeCommand(commandId: string, ...args: any[]): void {
|
|
||||||
this._apiWrapper.executeCommand(commandId, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
public showErrorMessage(errorMessage: string): void {
|
|
||||||
this._apiWrapper.showErrorMessage(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _context: ExtensionContext = undefined;
|
|
||||||
private _apiWrapper: ApiWrapper = undefined;
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import * as sqlops from 'sqlops';
|
|
||||||
import { TokenCredentials, ServiceClientCredentials } from 'ms-rest';
|
|
||||||
import { ApiWrapper } from '../../apiWrapper';
|
|
||||||
import * as nls from 'vscode-nls';
|
|
||||||
const localize = nls.loadMessageBundle();
|
|
||||||
|
|
||||||
import { IAzureResourceCredentialService } from '../interfaces';
|
|
||||||
import { AzureResourceCredentialError } from '../errors';
|
|
||||||
|
|
||||||
export class AzureResourceCredentialService implements IAzureResourceCredentialService {
|
|
||||||
public constructor(
|
|
||||||
apiWrapper: ApiWrapper
|
|
||||||
) {
|
|
||||||
this._apiWrapper = apiWrapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]> {
|
|
||||||
try {
|
|
||||||
let credentials: TokenCredentials[] = [];
|
|
||||||
let tokens = await this._apiWrapper.getSecurityToken(account, resource);
|
|
||||||
|
|
||||||
for (let tenant of account.properties.tenants) {
|
|
||||||
let token = tokens[tenant.id].token;
|
|
||||||
let tokenType = tokens[tenant.id].tokenType;
|
|
||||||
|
|
||||||
credentials.push(new TokenCredentials(token, tokenType));
|
|
||||||
}
|
|
||||||
|
|
||||||
return credentials;
|
|
||||||
} catch (error) {
|
|
||||||
throw new AzureResourceCredentialError(localize('azureResource.services.credentialService.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', account.key.accountId), error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _apiWrapper: ApiWrapper = undefined;
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
import { SqlManagementClient } from 'azure-arm-sql';
|
|
||||||
|
|
||||||
import { IAzureResourceDatabaseServerService } from '../interfaces';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../models';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService {
|
|
||||||
public async getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]> {
|
|
||||||
let databaseServers: AzureResourceDatabaseServer[] = [];
|
|
||||||
for (let cred of credentials) {
|
|
||||||
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
|
|
||||||
try {
|
|
||||||
let svrs = await sqlManagementClient.servers.list();
|
|
||||||
svrs.forEach((svr) => databaseServers.push({
|
|
||||||
name: svr.name,
|
|
||||||
fullName: svr.fullyQualifiedDomainName,
|
|
||||||
loginName: svr.administratorLogin,
|
|
||||||
defaultDatabaseName: 'master'
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
|
|
||||||
/**
|
|
||||||
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
|
|
||||||
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return databaseServers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
import { SqlManagementClient } from 'azure-arm-sql';
|
|
||||||
|
|
||||||
import { IAzureResourceDatabaseService } from '../interfaces';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabase } from '../models';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseService implements IAzureResourceDatabaseService {
|
|
||||||
public async getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]> {
|
|
||||||
let databases: AzureResourceDatabase[] = [];
|
|
||||||
for (let cred of credentials) {
|
|
||||||
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
|
|
||||||
try {
|
|
||||||
let svrs = await sqlManagementClient.servers.list();
|
|
||||||
for (let svr of svrs) {
|
|
||||||
// Extract resource group name from svr.id
|
|
||||||
let svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
|
|
||||||
if (!svrIdRegExp.test(svr.id)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let founds = svrIdRegExp.exec(svr.id);
|
|
||||||
let resouceGroup = founds[1];
|
|
||||||
|
|
||||||
let dbs = await sqlManagementClient.databases.listByServer(resouceGroup, svr.name);
|
|
||||||
dbs.forEach((db) => databases.push({
|
|
||||||
name: db.name,
|
|
||||||
serverName: svr.name,
|
|
||||||
serverFullName: svr.fullyQualifiedDomainName,
|
|
||||||
loginName: svr.administratorLogin
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
|
|
||||||
/**
|
|
||||||
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
|
|
||||||
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return databases;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,11 +8,11 @@
|
|||||||
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
|
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
|
||||||
import { Account } from 'sqlops';
|
import { Account } from 'sqlops';
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
||||||
import { AzureResourceSubscription } from '../models';
|
|
||||||
|
|
||||||
interface AzureResourceSelectedSubscriptionsCache {
|
interface AzureResourceSelectedSubscriptionsCache {
|
||||||
selectedSubscriptions: { [accountId: string]: AzureResourceSubscription[]};
|
selectedSubscriptions: { [accountId: string]: azureResource.AzureResourceSubscription[]};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
||||||
@@ -20,12 +20,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
cacheService: IAzureResourceCacheService
|
cacheService: IAzureResourceCacheService
|
||||||
) {
|
) {
|
||||||
this._cacheService = cacheService;
|
this._cacheService = cacheService;
|
||||||
|
|
||||||
|
this._cacheKey = this._cacheService.generateKey('selectedSubscriptions');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]> {
|
public async getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
let selectedSubscriptions: AzureResourceSubscription[] = [];
|
let selectedSubscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||||
|
|
||||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
||||||
}
|
}
|
||||||
@@ -33,10 +35,10 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
return selectedSubscriptions;
|
return selectedSubscriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void> {
|
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
|
||||||
let selectedSubscriptionsCache: { [accountId: string]: AzureResourceSubscription[]} = {};
|
let selectedSubscriptionsCache: { [accountId: string]: azureResource.AzureResourceSubscription[]} = {};
|
||||||
|
|
||||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
||||||
}
|
}
|
||||||
@@ -47,14 +49,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
|
|
||||||
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
||||||
|
|
||||||
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||||
|
|
||||||
const filters: string[] = [];
|
const filters: string[] = [];
|
||||||
for (const accountId in selectedSubscriptionsCache) {
|
for (const accountId in selectedSubscriptionsCache) {
|
||||||
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
|
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.FilterConfigName);
|
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.filterConfigName);
|
||||||
let configTarget = ConfigurationTarget.Global;
|
let configTarget = ConfigurationTarget.Global;
|
||||||
if (resourceFilterConfig) {
|
if (resourceFilterConfig) {
|
||||||
if (resourceFilterConfig.workspaceFolderValue) {
|
if (resourceFilterConfig.workspaceFolderValue) {
|
||||||
@@ -66,12 +68,12 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._config.update(AzureResourceSubscriptionFilterService.FilterConfigName, filters, configTarget);
|
await this._config.update(AzureResourceSubscriptionFilterService.filterConfigName, filters, configTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _config: WorkspaceConfiguration = undefined;
|
private _config: WorkspaceConfiguration = undefined;
|
||||||
private _cacheService: IAzureResourceCacheService = undefined;
|
private _cacheService: IAzureResourceCacheService = undefined;
|
||||||
|
private _cacheKey: string = undefined;
|
||||||
|
|
||||||
private static readonly FilterConfigName = 'resourceFilter';
|
private static readonly filterConfigName = 'azure.resource.config.filter';
|
||||||
private static readonly CacheKey = 'azureResource.cache.selectedSubscriptions';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,24 +9,19 @@ import { Account } from 'sqlops';
|
|||||||
import { ServiceClientCredentials } from 'ms-rest';
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
import { SubscriptionClient } from 'azure-arm-resource';
|
import { SubscriptionClient } from 'azure-arm-resource';
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
import { IAzureResourceSubscriptionService } from '../interfaces';
|
import { IAzureResourceSubscriptionService } from '../interfaces';
|
||||||
import { AzureResourceSubscription } from '../models';
|
|
||||||
|
|
||||||
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
||||||
public async getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]> {
|
public async getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
let subscriptions: AzureResourceSubscription[] = [];
|
const subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||||
for (let cred of credentials) {
|
|
||||||
let subClient = new SubscriptionClient.SubscriptionClient(cred);
|
const subClient = new SubscriptionClient.SubscriptionClient(credential);
|
||||||
try {
|
const subs = await subClient.subscriptions.list();
|
||||||
let subs = await subClient.subscriptions.list();
|
subs.forEach((sub) => subscriptions.push({
|
||||||
subs.forEach((sub) => subscriptions.push({
|
id: sub.subscriptionId,
|
||||||
id: sub.subscriptionId,
|
name: sub.displayName
|
||||||
name: sub.displayName
|
}));
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
// Swallow the exception here.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return subscriptions;
|
return subscriptions;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as request from 'request';
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
|
import { IAzureResourceTenantService } from '../interfaces';
|
||||||
|
|
||||||
|
export class AzureResourceTenantService implements IAzureResourceTenantService {
|
||||||
|
public async getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string> {
|
||||||
|
const requestPromisified = new Promise<string>((resolve, reject) => {
|
||||||
|
const url = `https://management.azure.com/subscriptions/${subscription.id}?api-version=2014-04-01`;
|
||||||
|
request(url, function (error, response, body) {
|
||||||
|
if (response.statusCode === 401) {
|
||||||
|
const tenantIdRegEx = /authorization_uri="https:\/\/login\.windows\.net\/([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})"/;
|
||||||
|
const teantIdString = response.headers['www-authenticate'];
|
||||||
|
if (tenantIdRegEx.test(teantIdString)) {
|
||||||
|
resolve(tenantIdRegEx.exec(teantIdString)[1]);
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return await requestPromisified;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { NodeInfo } from 'sqlops';
|
import { NodeInfo } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { TreeNode } from '../treeNode';
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { AzureResourceItemType } from '../constants';
|
||||||
|
|
||||||
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||||
@@ -19,11 +19,11 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||||
let item = new TreeItem(AzureResourceAccountNotSignedInTreeNode.SignInLabel, TreeItemCollapsibleState.None);
|
let item = new TreeItem(AzureResourceAccountNotSignedInTreeNode.signInLabel, TreeItemCollapsibleState.None);
|
||||||
item.contextValue = AzureResourceItemType.message;
|
item.contextValue = AzureResourceItemType.message;
|
||||||
item.command = {
|
item.command = {
|
||||||
title: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
title: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||||
command: 'azureresource.signin',
|
command: 'azure.resource.signin',
|
||||||
arguments: [this]
|
arguments: [this]
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
@@ -31,7 +31,7 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
|||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
public getNodeInfo(): NodeInfo {
|
||||||
return {
|
return {
|
||||||
label: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
label: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
errorMessage: undefined,
|
errorMessage: undefined,
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
@@ -47,5 +47,5 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
|||||||
return 'message_accountNotSignedIn';
|
return 'message_accountNotSignedIn';
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly SignInLabel = localize('azureResource.tree.accountNotSignedInTreeNode.signIn', 'Sign in to Azure ...');
|
private static readonly signInLabel = localize('azure.resource.tree.accountNotSignedInTreeNode.signInLabel', 'Sign in to Azure ...');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,44 +6,59 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
import { Account, NodeInfo, AzureResource } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
import { TokenCredentials } from 'ms-rest';
|
||||||
|
import { AppContext } from '../../appContext';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
|
import { TreeNode } from '../treeNode';
|
||||||
|
import { AzureResourceCredentialError } from '../errors';
|
||||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
||||||
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
import { AzureResourceSubscription } from '../models';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureResourceTenantService } from '../../azureResource/interfaces';
|
||||||
|
|
||||||
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||||
public constructor(
|
public constructor(
|
||||||
account: Account,
|
public readonly account: Account,
|
||||||
|
appContext: AppContext,
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler
|
treeChangeHandler: IAzureResourceTreeChangeHandler
|
||||||
) {
|
) {
|
||||||
super(account, treeChangeHandler, undefined);
|
super(appContext, treeChangeHandler, undefined);
|
||||||
|
|
||||||
|
this._subscriptionService = this.appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||||
|
this._subscriptionFilterService = this.appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||||
|
this._tenantService = this.appContext.getService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService);
|
||||||
|
|
||||||
this._id = `account_${this.account.key.accountId}`;
|
this._id = `account_${this.account.key.accountId}`;
|
||||||
|
this.setCacheKey(`${this._id}.subscriptions`);
|
||||||
this._label = this.generateLabel();
|
this._label = this.generateLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
public async getChildren(): Promise<TreeNode[]> {
|
||||||
try {
|
try {
|
||||||
let subscriptions: AzureResourceSubscription[] = [];
|
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||||
|
|
||||||
if (this._isClearingCache) {
|
if (this._isClearingCache) {
|
||||||
const credentials = await this.getCredentials();
|
try {
|
||||||
subscriptions = (await this.servicePool.subscriptionService.getSubscriptions(this.account, credentials)) || <AzureResourceSubscription[]>[];
|
const tokens = await this.appContext.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
|
||||||
|
|
||||||
let cache = this.getCache<AzureResourceSubscriptionsCache>();
|
for (const tenant of this.account.properties.tenants) {
|
||||||
if (!cache) {
|
const token = tokens[tenant.id].token;
|
||||||
cache = { subscriptions: { } };
|
const tokenType = tokens[tenant.id].tokenType;
|
||||||
|
|
||||||
|
subscriptions.push(...(await this._subscriptionService.getSubscriptions(this.account, new TokenCredentials(token, tokenType)) || <azureResource.AzureResourceSubscription[]>[]));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
throw new AzureResourceCredentialError(localize('azure.resource.tree.accountTreeNode.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', this.account.key.accountId), error);
|
||||||
}
|
}
|
||||||
cache.subscriptions[this.account.key.accountId] = subscriptions;
|
|
||||||
this.updateCache<AzureResourceSubscriptionsCache>(cache);
|
this.updateCache<azureResource.AzureResourceSubscription[]>(subscriptions);
|
||||||
|
|
||||||
this._isClearingCache = false;
|
this._isClearingCache = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -52,8 +67,8 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
|
|
||||||
this._totalSubscriptionCount = subscriptions.length;
|
this._totalSubscriptionCount = subscriptions.length;
|
||||||
|
|
||||||
let selectedSubscriptions = await this.servicePool.subscriptionFilterService.getSelectedSubscriptions(this.account);
|
const selectedSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account);
|
||||||
let selectedSubscriptionIds = (selectedSubscriptions || <AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||||
if (selectedSubscriptionIds.length > 0) {
|
if (selectedSubscriptionIds.length > 0) {
|
||||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||||
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
||||||
@@ -65,31 +80,33 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
this.refreshLabel();
|
this.refreshLabel();
|
||||||
|
|
||||||
if (subscriptions.length === 0) {
|
if (subscriptions.length === 0) {
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.NoSubscriptions, this)];
|
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
|
||||||
} else {
|
} else {
|
||||||
return subscriptions.map((subscription) => new AzureResourceSubscriptionTreeNode(subscription, this.account, this.treeChangeHandler, this));
|
return await Promise.all(subscriptions.map(async (subscription) => {
|
||||||
|
const tenantId = await this._tenantService.getTenantId(subscription);
|
||||||
|
|
||||||
|
return new AzureResourceSubscriptionTreeNode(this.account, subscription, tenantId, this.appContext, this.treeChangeHandler, this);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (error instanceof AzureResourceCredentialError) {
|
||||||
|
this.appContext.apiWrapper.executeCommand('azure.resource.signin');
|
||||||
|
}
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getCachedSubscriptions(): Promise<AzureResourceSubscription[]> {
|
public async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
const subscriptions: AzureResourceSubscription[] = [];
|
return this.getCache<azureResource.AzureResourceSubscription[]>();
|
||||||
const cache = this.getCache<AzureResourceSubscriptionsCache>();
|
|
||||||
if (cache) {
|
|
||||||
subscriptions.push(...cache.subscriptions[this.account.key.accountId]);
|
|
||||||
}
|
|
||||||
return subscriptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||||
let item = new TreeItem(this._label, TreeItemCollapsibleState.Collapsed);
|
const item = new TreeItem(this._label, TreeItemCollapsibleState.Collapsed);
|
||||||
item.id = this._id;
|
item.id = this._id;
|
||||||
item.contextValue = AzureResourceItemType.account;
|
item.contextValue = AzureResourceItemType.account;
|
||||||
item.iconPath = {
|
item.iconPath = {
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/account_inverse.svg'),
|
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/account_inverse.svg'),
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/account.svg')
|
light: this.appContext.extensionContext.asAbsolutePath('resources/light/account.svg')
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -128,10 +145,6 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get cacheKey(): string {
|
|
||||||
return 'azureResource.cache.subscriptions';
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateLabel(): string {
|
private generateLabel(): string {
|
||||||
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
|
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
|
||||||
|
|
||||||
@@ -142,14 +155,14 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _subscriptionService: IAzureResourceSubscriptionService = undefined;
|
||||||
|
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService = undefined;
|
||||||
|
private _tenantService: IAzureResourceTenantService = undefined;
|
||||||
|
|
||||||
private _id: string = undefined;
|
private _id: string = undefined;
|
||||||
private _label: string = undefined;
|
private _label: string = undefined;
|
||||||
private _totalSubscriptionCount = 0;
|
private _totalSubscriptionCount = 0;
|
||||||
private _selectedSubscriptionCount = 0;
|
private _selectedSubscriptionCount = 0;
|
||||||
|
|
||||||
private static readonly NoSubscriptions = localize('azureResource.tree.accountTreeNode.noSubscriptions', 'No Subscriptions found.');
|
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', 'No Subscriptions found.');
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AzureResourceSubscriptionsCache {
|
|
||||||
subscriptions: { [accountId: string]: AzureResourceSubscription[] };
|
|
||||||
}
|
|
||||||
@@ -5,16 +5,16 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import * as sqlops from 'sqlops';
|
import { AppContext } from '../../appContext';
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../servicePool';
|
import { TreeNode } from '../treeNode';
|
||||||
import { AzureResourceCredentialError } from '../errors';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
|
import { IAzureResourceCacheService } from '../../azureResource/interfaces';
|
||||||
|
import { AzureResourceServiceNames } from '../constants';
|
||||||
|
|
||||||
export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
public readonly appContext: AppContext,
|
||||||
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
|
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||||
parent: TreeNode
|
parent: TreeNode
|
||||||
) {
|
) {
|
||||||
@@ -22,17 +22,17 @@ export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
|||||||
|
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly servicePool = AzureResourceServicePool.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
|
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly account: sqlops.Account,
|
appContext: AppContext,
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||||
parent: TreeNode
|
parent: TreeNode
|
||||||
) {
|
) {
|
||||||
super(treeChangeHandler, parent);
|
super(appContext, treeChangeHandler, parent);
|
||||||
|
|
||||||
|
this._cacheService = this.appContext.getService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearCache(): void {
|
public clearCache(): void {
|
||||||
@@ -43,29 +43,19 @@ export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTr
|
|||||||
return this._isClearingCache;
|
return this._isClearingCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getCredentials(): Promise<ServiceClientCredentials[]> {
|
protected setCacheKey(id: string): void {
|
||||||
try {
|
this._cacheKey = this._cacheService.generateKey(id);
|
||||||
return await this.servicePool.credentialService.getCredentials(this.account, sqlops.AzureResource.ResourceManagement);
|
}
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof AzureResourceCredentialError) {
|
|
||||||
this.servicePool.contextService.showErrorMessage(error.message);
|
|
||||||
|
|
||||||
this.servicePool.contextService.executeCommand('azureresource.signin');
|
|
||||||
} else {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updateCache<T>(cache: T): void {
|
protected updateCache<T>(cache: T): void {
|
||||||
this.servicePool.cacheService.update<T>(this.cacheKey, cache);
|
this._cacheService.update<T>(this._cacheKey, cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getCache<T>(): T {
|
protected getCache<T>(): T {
|
||||||
return this.servicePool.cacheService.get<T>(this.cacheKey);
|
return this._cacheService.get<T>(this._cacheKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract get cacheKey(): string;
|
|
||||||
|
|
||||||
protected _isClearingCache = true;
|
protected _isClearingCache = true;
|
||||||
|
private _cacheService: IAzureResourceCacheService = undefined;
|
||||||
|
private _cacheKey: string = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import * as nls from 'vscode-nls';
|
|
||||||
const localize = nls.loadMessageBundle();
|
|
||||||
|
|
||||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
|
||||||
import { AzureResourceDatabaseTreeNode } from './databaseTreeNode';
|
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabase } from '../models';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseContainerTreeNode extends AzureResourceContainerTreeNodeBase {
|
|
||||||
public constructor(
|
|
||||||
public readonly subscription: AzureResourceSubscription,
|
|
||||||
account: Account,
|
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
|
||||||
parent: TreeNode
|
|
||||||
) {
|
|
||||||
super(account, treeChangeHandler, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
|
||||||
try {
|
|
||||||
let databases: AzureResourceDatabase[] = [];
|
|
||||||
|
|
||||||
if (this._isClearingCache) {
|
|
||||||
let credentials = await this.getCredentials();
|
|
||||||
databases = (await this.servicePool.databaseService.getDatabases(this.subscription, credentials)) || <AzureResourceDatabase[]>[];
|
|
||||||
|
|
||||||
let cache = this.getCache<AzureResourceDatabasesCache>();
|
|
||||||
if (!cache) {
|
|
||||||
cache = { databases: { } };
|
|
||||||
}
|
|
||||||
cache.databases[this.subscription.id] = databases;
|
|
||||||
this.updateCache(cache);
|
|
||||||
|
|
||||||
this._isClearingCache = false;
|
|
||||||
} else {
|
|
||||||
const cache = this.getCache<AzureResourceDatabasesCache>();
|
|
||||||
if (cache) {
|
|
||||||
databases = cache.databases[this.subscription.id] || <AzureResourceDatabase[]>[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (databases.length === 0) {
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceDatabaseContainerTreeNode.NoDatabases, this)];
|
|
||||||
} else {
|
|
||||||
return databases.map((database) => new AzureResourceDatabaseTreeNode(database, this.treeChangeHandler, this));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
|
||||||
let item = new TreeItem(AzureResourceDatabaseContainerTreeNode.Label, TreeItemCollapsibleState.Collapsed);
|
|
||||||
item.contextValue = AzureResourceItemType.databaseContainer;
|
|
||||||
item.iconPath = {
|
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/folder_inverse.svg'),
|
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/folder.svg')
|
|
||||||
};
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
|
||||||
return {
|
|
||||||
label: AzureResourceDatabaseContainerTreeNode.Label,
|
|
||||||
isLeaf: false,
|
|
||||||
errorMessage: undefined,
|
|
||||||
metadata: undefined,
|
|
||||||
nodePath: this.generateNodePath(),
|
|
||||||
nodeStatus: undefined,
|
|
||||||
nodeType: AzureResourceItemType.databaseContainer,
|
|
||||||
nodeSubType: undefined,
|
|
||||||
iconType: AzureResourceItemType.databaseContainer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
|
||||||
return 'databaseContainer';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get cacheKey(): string {
|
|
||||||
return 'azureResource.cache.databases';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Label = localize('azureResource.tree.databaseContainerTreeNode.label', 'SQL Databases');
|
|
||||||
private static readonly NoDatabases = localize('azureResource.tree.databaseContainerTreeNode.noDatabases', 'No SQL Databases found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AzureResourceDatabasesCache {
|
|
||||||
databases: { [subscriptionId: string]: AzureResourceDatabase[] };
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import * as nls from 'vscode-nls';
|
|
||||||
const localize = nls.loadMessageBundle();
|
|
||||||
|
|
||||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../models';
|
|
||||||
import { AzureResourceDatabaseServerTreeNode } from './databaseServerTreeNode';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseServerContainerTreeNode extends AzureResourceContainerTreeNodeBase {
|
|
||||||
public constructor(
|
|
||||||
public readonly subscription: AzureResourceSubscription,
|
|
||||||
account: Account,
|
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
|
||||||
parent: TreeNode
|
|
||||||
) {
|
|
||||||
super(account, treeChangeHandler, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
|
||||||
try {
|
|
||||||
let databaseServers: AzureResourceDatabaseServer[] = [];
|
|
||||||
|
|
||||||
if (this._isClearingCache) {
|
|
||||||
let credentials = await this.getCredentials();
|
|
||||||
databaseServers = (await this.servicePool.databaseServerService.getDatabaseServers(this.subscription, credentials)) || <AzureResourceDatabaseServer[]>[];
|
|
||||||
|
|
||||||
let cache = this.getCache<AzureResourceDatabaseServersCache>();
|
|
||||||
if (!cache) {
|
|
||||||
cache = { databaseServers: { } };
|
|
||||||
}
|
|
||||||
cache.databaseServers[this.subscription.id] = databaseServers;
|
|
||||||
this.updateCache<AzureResourceDatabaseServersCache>(cache);
|
|
||||||
|
|
||||||
this._isClearingCache = false;
|
|
||||||
} else {
|
|
||||||
const cache = this.getCache<AzureResourceDatabaseServersCache>();
|
|
||||||
if (cache) {
|
|
||||||
databaseServers = cache.databaseServers[this.subscription.id] || <AzureResourceDatabaseServer[]>[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (databaseServers.length === 0) {
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceDatabaseServerContainerTreeNode.NoDatabaseServers, this)];
|
|
||||||
} else {
|
|
||||||
return databaseServers.map((server) => new AzureResourceDatabaseServerTreeNode(server, this.treeChangeHandler, this));
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
|
||||||
let item = new TreeItem(AzureResourceDatabaseServerContainerTreeNode.Label, TreeItemCollapsibleState.Collapsed);
|
|
||||||
item.contextValue = AzureResourceItemType.databaseServerContainer;
|
|
||||||
item.iconPath = {
|
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/folder_inverse.svg'),
|
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/folder.svg')
|
|
||||||
};
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
|
||||||
return {
|
|
||||||
label: AzureResourceDatabaseServerContainerTreeNode.Label,
|
|
||||||
isLeaf: false,
|
|
||||||
errorMessage: undefined,
|
|
||||||
metadata: undefined,
|
|
||||||
nodePath: this.generateNodePath(),
|
|
||||||
nodeStatus: undefined,
|
|
||||||
nodeType: AzureResourceItemType.databaseServerContainer,
|
|
||||||
nodeSubType: undefined,
|
|
||||||
iconType: AzureResourceItemType.databaseServerContainer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
|
||||||
return 'databaseServerContainer';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get cacheKey(): string {
|
|
||||||
return 'azureResource.cache.databaseServers';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly Label = localize('azureResource.tree.databaseServerContainerTreeNode.label', 'SQL Servers');
|
|
||||||
private static readonly NoDatabaseServers = localize('azureResource.tree.databaseContainerTreeNode.noDatabaseServers', 'No SQL Servers found.');
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AzureResourceDatabaseServersCache {
|
|
||||||
databaseServers: { [subscriptionId: string]: AzureResourceDatabaseServer[] };
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
|
||||||
import { NodeInfo } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
|
|
||||||
import { AzureResourceTreeNodeBase } from './baseTreeNodes';
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
|
||||||
import { AzureResourceDatabaseServer } from '../models';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseServerTreeNode extends AzureResourceTreeNodeBase {
|
|
||||||
public constructor(
|
|
||||||
public readonly databaseServer: AzureResourceDatabaseServer,
|
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
|
||||||
parent: TreeNode
|
|
||||||
) {
|
|
||||||
super(treeChangeHandler, parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
|
||||||
let item = new TreeItem(this.databaseServer.name, TreeItemCollapsibleState.None);
|
|
||||||
item.contextValue = AzureResourceItemType.databaseServer;
|
|
||||||
item.iconPath = {
|
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/sql_server.svg')
|
|
||||||
};
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
|
||||||
return {
|
|
||||||
label: this.databaseServer.name,
|
|
||||||
isLeaf: true,
|
|
||||||
errorMessage: undefined,
|
|
||||||
metadata: undefined,
|
|
||||||
nodePath: this.generateNodePath(),
|
|
||||||
nodeStatus: undefined,
|
|
||||||
nodeType: AzureResourceItemType.databaseServer,
|
|
||||||
nodeSubType: undefined,
|
|
||||||
iconType: AzureResourceItemType.databaseServer
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
|
||||||
return `databaseServer_${this.databaseServer.name}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
|
||||||
import { NodeInfo } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
|
|
||||||
import { AzureResourceTreeNodeBase } from './baseTreeNodes';
|
|
||||||
import { AzureResourceItemType } from '../constants';
|
|
||||||
import { AzureResourceDatabase } from '../models';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
|
||||||
|
|
||||||
export class AzureResourceDatabaseTreeNode extends AzureResourceTreeNodeBase {
|
|
||||||
public constructor(
|
|
||||||
public readonly database: AzureResourceDatabase,
|
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
|
||||||
parent: TreeNode
|
|
||||||
) {
|
|
||||||
super(treeChangeHandler, parent);
|
|
||||||
|
|
||||||
this._label = `${this.database.name} (${this.database.serverName})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
|
||||||
let item = new TreeItem(this._label, TreeItemCollapsibleState.None);
|
|
||||||
item.contextValue = AzureResourceItemType.database;
|
|
||||||
item.iconPath = {
|
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/sql_database_inverse.svg'),
|
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/sql_database.svg')
|
|
||||||
};
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
|
||||||
return {
|
|
||||||
label: this._label,
|
|
||||||
isLeaf: true,
|
|
||||||
errorMessage: undefined,
|
|
||||||
metadata: undefined,
|
|
||||||
nodePath: this.generateNodePath(),
|
|
||||||
nodeStatus: undefined,
|
|
||||||
nodeType: AzureResourceItemType.database,
|
|
||||||
nodeSubType: undefined,
|
|
||||||
iconType: AzureResourceItemType.database
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
|
||||||
return `database_${this.database.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _label: string = undefined;
|
|
||||||
}
|
|
||||||
@@ -7,38 +7,66 @@
|
|||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
import { Account, NodeInfo } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
import { AppContext } from '../../appContext';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
import { AzureResourceTreeNodeBase, AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
import { azureResource } from '../azure-resource';
|
||||||
|
import { TreeNode } from '../treeNode';
|
||||||
|
import { IAzureResourceNodeWithProviderId } from '../interfaces';
|
||||||
|
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { AzureResourceItemType } from '../constants';
|
||||||
import { AzureResourceDatabaseContainerTreeNode } from './databaseContainerTreeNode';
|
|
||||||
import { AzureResourceDatabaseServerContainerTreeNode } from './databaseServerContainerTreeNode';
|
|
||||||
import { AzureResourceSubscription } from '../models';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
|
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||||
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
|
import { AzureResourceService } from '../resourceService';
|
||||||
|
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
|
||||||
|
|
||||||
export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase {
|
export class AzureResourceSubscriptionTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly subscription: AzureResourceSubscription,
|
public readonly account: Account,
|
||||||
account: Account,
|
public readonly subscription: azureResource.AzureResourceSubscription,
|
||||||
|
public readonly tenatId: string,
|
||||||
|
appContext: AppContext,
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||||
parent: TreeNode
|
parent: TreeNode
|
||||||
) {
|
) {
|
||||||
super(treeChangeHandler, parent);
|
super(appContext, treeChangeHandler, parent);
|
||||||
|
|
||||||
this._children.push(new AzureResourceDatabaseContainerTreeNode(subscription, account, treeChangeHandler, this));
|
this._id = `account_${this.account.key.accountId}.subscription_${this.subscription.id}.tenant_${this.tenatId}`;
|
||||||
this._children.push(new AzureResourceDatabaseServerContainerTreeNode(subscription, account, treeChangeHandler, this));
|
this.setCacheKey(`${this._id}.resources`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
public async getChildren(): Promise<TreeNode[]> {
|
||||||
return this._children;
|
try {
|
||||||
|
const resourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
|
const children: IAzureResourceNodeWithProviderId[] = [];
|
||||||
|
|
||||||
|
for (const resourceProviderId of await resourceService.listResourceProviderIds()) {
|
||||||
|
children.push(...await resourceService.getRootChildren(resourceProviderId, this.account, this.subscription, this.tenatId));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (children.length === 0) {
|
||||||
|
return [AzureResourceMessageTreeNode.create(AzureResourceSubscriptionTreeNode.noResourcesLabel, this)];
|
||||||
|
} else {
|
||||||
|
return children.map((child) => {
|
||||||
|
// To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered'
|
||||||
|
child.resourceNode.treeItem.id = `${this._id}.${child.resourceNode.treeItem.id}`;
|
||||||
|
return new AzureResourceResourceTreeNode(child, this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||||
let item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
|
const item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
|
||||||
item.contextValue = AzureResourceItemType.subscription;
|
item.contextValue = AzureResourceItemType.subscription;
|
||||||
item.iconPath = {
|
item.iconPath = {
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/subscription_inverse.svg'),
|
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/subscription_inverse.svg'),
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/subscription.svg')
|
light: this.appContext.extensionContext.asAbsolutePath('resources/light/subscription.svg')
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -58,8 +86,10 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
public get nodePathValue(): string {
|
||||||
return `subscription_${this.subscription.id}`;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _children: AzureResourceContainerTreeNodeBase[] = [];
|
private _id: string = undefined;
|
||||||
|
|
||||||
|
private static readonly noResourcesLabel = localize('azure.resource.tree.subscriptionTreeNode.noResourcesLabel', 'No Resources found.');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
import { TreeNode } from '../treeNode';
|
||||||
|
|
||||||
export interface IAzureResourceTreeChangeHandler {
|
export interface IAzureResourceTreeChangeHandler {
|
||||||
notifyNodeChanged(node: TreeNode): void;
|
notifyNodeChanged(node: TreeNode): void;
|
||||||
|
|||||||
@@ -6,26 +6,25 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
|
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
|
||||||
import { DidChangeAccountsParams } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import { setInterval, clearInterval } from 'timers';
|
import { setInterval, clearInterval } from 'timers';
|
||||||
|
import { AppContext } from '../../appContext';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../servicePool';
|
import { TreeNode } from '../treeNode';
|
||||||
import { AzureResourceAccountTreeNode } from './accountTreeNode';
|
import { AzureResourceAccountTreeNode } from './accountTreeNode';
|
||||||
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
|
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||||
import { AzureResourceContainerTreeNodeBase, AzureResourceTreeNodeBase } from './baseTreeNodes';
|
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
export interface IAzureResourceTreeChangeHandler {
|
import { IAzureResourceAccountService } from '../../azureResource/interfaces';
|
||||||
notifyNodeChanged(node: TreeNode): void;
|
import { AzureResourceServiceNames } from '../constants';
|
||||||
}
|
|
||||||
|
|
||||||
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
|
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
|
||||||
public constructor() {
|
public constructor(
|
||||||
AzureResourceServicePool.getInstance().accountService.onDidChangeAccounts((e: DidChangeAccountsParams) => { this._onDidChangeTreeData.fire(undefined); });
|
public readonly appContext: AppContext
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
||||||
@@ -33,11 +32,11 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
|||||||
return element.getChildren(true);
|
return element.getChildren(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isSystemInitialized) {
|
if (!this.isSystemInitialized && !this._loadingTimer) {
|
||||||
this._loadingTimer = setInterval(async () => {
|
this._loadingTimer = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
// Call sqlops.accounts.getAllAccounts() to determine whether the system has been initialized.
|
// Call sqlops.accounts.getAllAccounts() to determine whether the system has been initialized.
|
||||||
await AzureResourceServicePool.getInstance().accountService.getAccounts();
|
await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
|
||||||
|
|
||||||
// System has been initialized
|
// System has been initialized
|
||||||
this.isSystemInitialized = true;
|
this.isSystemInitialized = true;
|
||||||
@@ -51,16 +50,16 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
|||||||
// System not initialized yet
|
// System not initialized yet
|
||||||
this.isSystemInitialized = false;
|
this.isSystemInitialized = false;
|
||||||
}
|
}
|
||||||
}, AzureResourceTreeProvider.LoadingTimerInterval);
|
}, AzureResourceTreeProvider.loadingTimerInterval);
|
||||||
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.Loading, undefined)];
|
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.loadingLabel, undefined)];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accounts = await AzureResourceServicePool.getInstance().accountService.getAccounts();
|
const accounts = await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
|
||||||
|
|
||||||
if (accounts && accounts.length > 0) {
|
if (accounts && accounts.length > 0) {
|
||||||
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this));
|
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
|
||||||
} else {
|
} else {
|
||||||
return [new AzureResourceAccountNotSignedInTreeNode()];
|
return [new AzureResourceAccountNotSignedInTreeNode()];
|
||||||
}
|
}
|
||||||
@@ -96,6 +95,6 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
|||||||
private _loadingTimer: NodeJS.Timer = undefined;
|
private _loadingTimer: NodeJS.Timer = undefined;
|
||||||
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
|
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
|
||||||
|
|
||||||
private static readonly Loading = localize('azureResource.tree.treeProvider.loading', 'Loading ...');
|
private static readonly loadingLabel = localize('azure.resource.tree.treeProvider.loadingLabel', 'Loading ...');
|
||||||
private static readonly LoadingTimerInterval = 5000;
|
private static readonly loadingTimerInterval = 5000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,6 @@ import * as vscode from 'vscode';
|
|||||||
type TreeNodePredicate = (node: TreeNode) => boolean;
|
type TreeNodePredicate = (node: TreeNode) => boolean;
|
||||||
|
|
||||||
export abstract class TreeNode {
|
export abstract class TreeNode {
|
||||||
private _parent: TreeNode = undefined;
|
|
||||||
|
|
||||||
public get parent(): TreeNode {
|
|
||||||
return this._parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set parent(node: TreeNode) {
|
|
||||||
this._parent = node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public generateNodePath(): string {
|
public generateNodePath(): string {
|
||||||
let path = undefined;
|
let path = undefined;
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
@@ -65,13 +55,23 @@ export abstract class TreeNode {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get parent(): TreeNode {
|
||||||
|
return this._parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set parent(node: TreeNode) {
|
||||||
|
this._parent = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
||||||
|
public abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
||||||
|
|
||||||
|
public abstract getNodeInfo(): sqlops.NodeInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value to use for this node in the node path
|
* The value to use for this node in the node path
|
||||||
*/
|
*/
|
||||||
public abstract get nodePathValue(): string;
|
public abstract get nodePathValue(): string;
|
||||||
|
|
||||||
abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
private _parent: TreeNode = undefined;
|
||||||
abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
|
||||||
|
|
||||||
abstract getNodeInfo(): sqlops.NodeInfo;
|
|
||||||
}
|
}
|
||||||
@@ -12,10 +12,9 @@ export function getErrorMessage(error: Error | string): string {
|
|||||||
return (error instanceof Error) ? error.message : error;
|
return (error instanceof Error) ? error.message : error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class AzureResourceErrorMessageUtil {
|
export class AzureResourceErrorMessageUtil {
|
||||||
public static getErrorMessage(error: Error | string): string {
|
public static getErrorMessage(error: Error | string): string {
|
||||||
return localize('azureResource.error', 'Error: {0}', getErrorMessage(error));
|
return localize('azure.resource.error', 'Error: {0}', getErrorMessage(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,4 +40,57 @@ export function generateGuid(): string {
|
|||||||
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||||
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||||
/* tslint:enable:no-bitwise */
|
/* tslint:enable:no-bitwise */
|
||||||
|
}
|
||||||
|
|
||||||
|
export function equals(one: any, other: any): boolean {
|
||||||
|
if (one === other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (one === null || one === undefined || other === null || other === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof one !== typeof other) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (typeof one !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((Array.isArray(one)) !== (Array.isArray(other))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let i: number;
|
||||||
|
let key: string;
|
||||||
|
|
||||||
|
if (Array.isArray(one)) {
|
||||||
|
if (one.length !== other.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (i = 0; i < one.length; i++) {
|
||||||
|
if (!equals(one[i], other[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const oneKeys: string[] = [];
|
||||||
|
|
||||||
|
for (key in one) {
|
||||||
|
oneKeys.push(key);
|
||||||
|
}
|
||||||
|
oneKeys.sort();
|
||||||
|
const otherKeys: string[] = [];
|
||||||
|
for (key in other) {
|
||||||
|
otherKeys.push(key);
|
||||||
|
}
|
||||||
|
otherKeys.sort();
|
||||||
|
if (!equals(oneKeys, otherKeys)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (i = 0; i < oneKeys.length; i++) {
|
||||||
|
if (!equals(one[oneKeys[i]], other[oneKeys[i]])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import ControllerBase from './controllerBase';
|
||||||
|
import { DidChangeAccountsParams } from 'sqlops';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IAzureResourceCacheService,
|
||||||
|
IAzureResourceAccountService,
|
||||||
|
IAzureResourceSubscriptionService,
|
||||||
|
IAzureResourceSubscriptionFilterService,
|
||||||
|
IAzureResourceTenantService } from '../azureResource/interfaces';
|
||||||
|
import { AzureResourceServiceNames } from '../azureResource/constants';
|
||||||
|
import { AzureResourceTreeProvider } from '../azureResource/tree/treeProvider';
|
||||||
|
import { registerAzureResourceCommands } from '../azureResource/commands';
|
||||||
|
import { AzureResourceAccountService } from '../azureResource/services/accountService';
|
||||||
|
import { AzureResourceSubscriptionService } from '../azureResource/services/subscriptionService';
|
||||||
|
import { AzureResourceSubscriptionFilterService } from '../azureResource/services/subscriptionFilterService';
|
||||||
|
import { AzureResourceCacheService } from '../azureResource/services/cacheService';
|
||||||
|
import { AzureResourceTenantService } from '../azureResource/services/tenantService';
|
||||||
|
|
||||||
|
import { registerAzureResourceDatabaseServerCommands } from '../azureResource/providers/databaseServer/commands';
|
||||||
|
import { registerAzureResourceDatabaseCommands } from '../azureResource/providers/database/commands';
|
||||||
|
import { equals } from '../azureResource/utils';
|
||||||
|
|
||||||
|
export default class AzureResourceController extends ControllerBase {
|
||||||
|
public activate(): Promise<boolean> {
|
||||||
|
this.appContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, new AzureResourceCacheService(this.extensionContext));
|
||||||
|
this.appContext.registerService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService, new AzureResourceAccountService(this.apiWrapper));
|
||||||
|
this.appContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, new AzureResourceSubscriptionService());
|
||||||
|
this.appContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(this.extensionContext)));
|
||||||
|
this.appContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, new AzureResourceTenantService());
|
||||||
|
|
||||||
|
const azureResourceTree = new AzureResourceTreeProvider(this.appContext);
|
||||||
|
this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
|
||||||
|
|
||||||
|
let previousAccounts = undefined;
|
||||||
|
this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).onDidChangeAccounts((e: DidChangeAccountsParams) => {
|
||||||
|
// the onDidChangeAccounts event will trigger in many cases where the accounts didn't actually change
|
||||||
|
// the notifyNodeChanged event triggers a refresh which triggers a getChildren which can trigger this callback
|
||||||
|
// this below check short-circuits the infinite callback loop
|
||||||
|
if (!equals(e.accounts, previousAccounts)) {
|
||||||
|
azureResourceTree.notifyNodeChanged(undefined);
|
||||||
|
}
|
||||||
|
previousAccounts = e.accounts;
|
||||||
|
});
|
||||||
|
|
||||||
|
registerAzureResourceCommands(this.appContext, azureResourceTree);
|
||||||
|
|
||||||
|
registerAzureResourceDatabaseServerCommands(this.appContext);
|
||||||
|
|
||||||
|
registerAzureResourceDatabaseCommands(this.appContext);
|
||||||
|
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deactivate(): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import ControllerBase from './controllerBase';
|
|
||||||
|
|
||||||
import { AzureResourceTreeProvider } from '../azureResource/tree/treeProvider';
|
|
||||||
import { registerAzureResourceCommands } from '../azureResource/commands';
|
|
||||||
import { AzureResourceServicePool } from '../azureResource/servicePool';
|
|
||||||
import { AzureResourceCredentialService } from '../azureResource/services/credentialService';
|
|
||||||
import { AzureResourceAccountService } from '../azureResource/services/accountService';
|
|
||||||
import { AzureResourceSubscriptionService } from '../azureResource/services/subscriptionService';
|
|
||||||
import { AzureResourceSubscriptionFilterService } from '../azureResource/services/subscriptionFilterService';
|
|
||||||
import { AzureResourceDatabaseServerService } from '../azureResource/services/databaseServerService';
|
|
||||||
import { AzureResourceDatabaseService } from '../azureResource/services/databaseService';
|
|
||||||
import { AzureResourceCacheService } from '../azureResource/services/cacheService';
|
|
||||||
import { AzureResourceContextService } from '../azureResource/services/contextService';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The main controller class that initializes the extension
|
|
||||||
*/
|
|
||||||
export default class MainController extends ControllerBase {
|
|
||||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
|
||||||
/**
|
|
||||||
* Deactivates the extension
|
|
||||||
*/
|
|
||||||
public deactivate(): void {
|
|
||||||
}
|
|
||||||
|
|
||||||
public activate(): Promise<boolean> {
|
|
||||||
this.configureAzureResource();
|
|
||||||
return Promise.resolve(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private configureAzureResource(): void {
|
|
||||||
let servicePool = AzureResourceServicePool.getInstance();
|
|
||||||
servicePool.cacheService = new AzureResourceCacheService(this.extensionContext);
|
|
||||||
servicePool.contextService = new AzureResourceContextService(this.extensionContext, this.apiWrapper);
|
|
||||||
servicePool.accountService = new AzureResourceAccountService(this.apiWrapper);
|
|
||||||
servicePool.credentialService = new AzureResourceCredentialService(this.apiWrapper);
|
|
||||||
servicePool.subscriptionService = new AzureResourceSubscriptionService();
|
|
||||||
servicePool.subscriptionFilterService = new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(this.extensionContext));
|
|
||||||
servicePool.databaseService = new AzureResourceDatabaseService();
|
|
||||||
servicePool.databaseServerService = new AzureResourceDatabaseServerService();
|
|
||||||
|
|
||||||
let azureResourceTree = new AzureResourceTreeProvider();
|
|
||||||
this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
|
|
||||||
|
|
||||||
registerAzureResourceCommands(this.apiWrapper, azureResourceTree);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,12 +6,17 @@ import * as path from 'path';
|
|||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as constants from './constants';
|
import * as constants from './constants';
|
||||||
|
|
||||||
import MainController from './controllers/mainController';
|
import AzureResourceController from './controllers/azureResourceController';
|
||||||
import { AppContext } from './appContext';
|
import { AppContext } from './appContext';
|
||||||
import ControllerBase from './controllers/controllerBase';
|
import ControllerBase from './controllers/controllerBase';
|
||||||
import { ApiWrapper } from './apiWrapper';
|
import { ApiWrapper } from './apiWrapper';
|
||||||
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
|
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
|
||||||
|
|
||||||
|
import { AzureResourceDatabaseServerProvider } from './azureResource/providers/databaseServer/databaseServerProvider';
|
||||||
|
import { AzureResourceDatabaseServerService } from './azureResource/providers/databaseServer/databaseServerService';
|
||||||
|
import { AzureResourceDatabaseProvider } from './azureResource/providers/database/databaseProvider';
|
||||||
|
import { AzureResourceDatabaseService } from './azureResource/providers/database/databaseService';
|
||||||
|
|
||||||
let controllers: ControllerBase[] = [];
|
let controllers: ControllerBase[] = [];
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +40,8 @@ export function getDefaultLogLocation() {
|
|||||||
// this method is called when your extension is activated
|
// this method is called when your extension is activated
|
||||||
// your extension is activated the very first time the command is executed
|
// your extension is activated the very first time the command is executed
|
||||||
export function activate(extensionContext: vscode.ExtensionContext) {
|
export function activate(extensionContext: vscode.ExtensionContext) {
|
||||||
let appContext = new AppContext(extensionContext, new ApiWrapper());
|
const apiWrapper = new ApiWrapper();
|
||||||
|
let appContext = new AppContext(extensionContext, apiWrapper);
|
||||||
let activations: Thenable<boolean>[] = [];
|
let activations: Thenable<boolean>[] = [];
|
||||||
|
|
||||||
// Create the folder for storing the token caches
|
// Create the folder for storing the token caches
|
||||||
@@ -56,21 +62,19 @@ export function activate(extensionContext: vscode.ExtensionContext) {
|
|||||||
extensionContext.subscriptions.push(accountProviderService);
|
extensionContext.subscriptions.push(accountProviderService);
|
||||||
accountProviderService.activate();
|
accountProviderService.activate();
|
||||||
|
|
||||||
// Start the main controller
|
const azureResourceController = new AzureResourceController(appContext);
|
||||||
let mainController = new MainController(appContext);
|
controllers.push(azureResourceController);
|
||||||
controllers.push(mainController);
|
extensionContext.subscriptions.push(azureResourceController);
|
||||||
extensionContext.subscriptions.push(mainController);
|
activations.push(azureResourceController.activate());
|
||||||
activations.push(mainController.activate());
|
|
||||||
|
|
||||||
return Promise.all(activations)
|
return {
|
||||||
.then((results: boolean[]) => {
|
provideResources() {
|
||||||
for (let result of results) {
|
return [
|
||||||
if (!result) {
|
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), apiWrapper, extensionContext),
|
||||||
return false;
|
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext)
|
||||||
}
|
];
|
||||||
}
|
}
|
||||||
return true;
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method is called when your extension is deactivated
|
// this method is called when your extension is deactivated
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import * as should from 'should';
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
import { AzureResourceItemType } from '../../azureResource/constants';
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../../azureResource/messageTreeNode';
|
||||||
|
|
||||||
describe('AzureResourceMessageTreeNode.info', function(): void {
|
describe('AzureResourceMessageTreeNode.info', function(): void {
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { azureResource } from '../../../../azureResource/azure-resource';
|
||||||
|
import { ApiWrapper } from '../../../../apiWrapper';
|
||||||
|
import { IAzureResourceDatabaseService } from '../../../../azureResource/providers/database/interfaces';
|
||||||
|
import { AzureResourceDatabaseTreeDataProvider } from '../../../../azureResource/providers/database/databaseTreeDataProvider';
|
||||||
|
import { AzureResourceDatabase } from '../../../../azureResource/providers/database/models';
|
||||||
|
import { AzureResourceItemType } from '../../../../azureResource/constants';
|
||||||
|
|
||||||
|
// Mock services
|
||||||
|
let mockDatabaseService: TypeMoq.IMock<IAzureResourceDatabaseService>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
|
||||||
|
// Mock test data
|
||||||
|
const mockAccount: sqlops.Account = {
|
||||||
|
key: {
|
||||||
|
accountId: 'mock_account',
|
||||||
|
providerId: 'mock_provider'
|
||||||
|
},
|
||||||
|
displayInfo: {
|
||||||
|
displayName: 'mock_account@test.com',
|
||||||
|
accountType: 'Microsoft',
|
||||||
|
contextualDisplayName: 'test'
|
||||||
|
},
|
||||||
|
properties: undefined,
|
||||||
|
isStale: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
|
id: 'mock_subscription',
|
||||||
|
name: 'mock subscription'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_root_node',
|
||||||
|
label: 'mock resource root node',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: 'mock_resource_root_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTokens = {};
|
||||||
|
mockTokens[mockTenantId] = {
|
||||||
|
token: 'mock_token',
|
||||||
|
tokenType: 'Bearer'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockDatabases: AzureResourceDatabase[] = [
|
||||||
|
{
|
||||||
|
name: 'mock database 1',
|
||||||
|
serverName: 'mock database server 1',
|
||||||
|
serverFullName: 'mock database server full name 1',
|
||||||
|
loginName: 'mock login'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mock database 2',
|
||||||
|
serverName: 'mock database server 2',
|
||||||
|
serverFullName: 'mock database server full name 2',
|
||||||
|
loginName: 'mock login'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('AzureResourceDatabaseTreeDataProvider.info', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const treeItem = await treeDataProvider.getTreeItem(mockResourceRootNode);
|
||||||
|
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
|
||||||
|
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
|
||||||
|
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
|
||||||
|
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceDatabaseTreeDataProvider.getChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
|
||||||
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabases));
|
||||||
|
mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return container node when element is undefined.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const children = await treeDataProvider.getChildren();
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(1);
|
||||||
|
|
||||||
|
const child = children[0];
|
||||||
|
should(child.account).undefined();
|
||||||
|
should(child.subscription).undefined();
|
||||||
|
should(child.tenantId).undefined();
|
||||||
|
should(child.treeItem.id).equal('azure.resource.providers.database.treeDataProvider.databaseContainer');
|
||||||
|
should(child.treeItem.label).equal('SQL Databases');
|
||||||
|
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||||
|
should(child.treeItem.contextValue).equal('azure.resource.itemType.databaseContainer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const children = await treeDataProvider.getChildren(mockResourceRootNode);
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(mockDatabases.length);
|
||||||
|
|
||||||
|
for (let ix = 0; ix < children.length; ix++) {
|
||||||
|
const child = children[ix];
|
||||||
|
const database = mockDatabases[ix];
|
||||||
|
|
||||||
|
should(child.account).equal(mockAccount);
|
||||||
|
should(child.subscription).equal(mockSubscription);
|
||||||
|
should(child.tenantId).equal(mockTenantId);
|
||||||
|
should(child.treeItem.id).equal(`databaseServer_${database.serverFullName}.database_${database.name}`);
|
||||||
|
should(child.treeItem.label).equal(`${database.name} (${database.serverName})`);
|
||||||
|
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||||
|
should(child.treeItem.contextValue).equal(AzureResourceItemType.database);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { azureResource } from '../../../../azureResource/azure-resource';
|
||||||
|
import { ApiWrapper } from '../../../../apiWrapper';
|
||||||
|
import { IAzureResourceDatabaseServerService } from '../../../../azureResource/providers/databaseServer/interfaces';
|
||||||
|
import { AzureResourceDatabaseServerTreeDataProvider } from '../../../../azureResource/providers/databaseServer/databaseServerTreeDataProvider';
|
||||||
|
import { AzureResourceDatabaseServer } from '../../../../azureResource/providers/databaseServer/models';
|
||||||
|
import { AzureResourceItemType } from '../../../../azureResource/constants';
|
||||||
|
|
||||||
|
// Mock services
|
||||||
|
let mockDatabaseServerService: TypeMoq.IMock<IAzureResourceDatabaseServerService>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
|
||||||
|
// Mock test data
|
||||||
|
const mockAccount: sqlops.Account = {
|
||||||
|
key: {
|
||||||
|
accountId: 'mock_account',
|
||||||
|
providerId: 'mock_provider'
|
||||||
|
},
|
||||||
|
displayInfo: {
|
||||||
|
displayName: 'mock_account@test.com',
|
||||||
|
accountType: 'Microsoft',
|
||||||
|
contextualDisplayName: 'test'
|
||||||
|
},
|
||||||
|
properties: undefined,
|
||||||
|
isStale: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
|
id: 'mock_subscription',
|
||||||
|
name: 'mock subscription'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_root_node',
|
||||||
|
label: 'mock resource root node',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: 'mock_resource_root_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTokens = {};
|
||||||
|
mockTokens[mockTenantId] = {
|
||||||
|
token: 'mock_token',
|
||||||
|
tokenType: 'Bearer'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockDatabaseServers: AzureResourceDatabaseServer[] = [
|
||||||
|
{
|
||||||
|
name: 'mock database server 1',
|
||||||
|
fullName: 'mock database server full name 1',
|
||||||
|
loginName: 'mock login',
|
||||||
|
defaultDatabaseName: 'master'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'mock database server 2',
|
||||||
|
fullName: 'mock database server full name 2',
|
||||||
|
loginName: 'mock login',
|
||||||
|
defaultDatabaseName: 'master'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('AzureResourceDatabaseServerTreeDataProvider.info', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const treeItem = await treeDataProvider.getTreeItem(mockResourceRootNode);
|
||||||
|
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
|
||||||
|
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
|
||||||
|
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
|
||||||
|
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceDatabaseServerTreeDataProvider.getChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
|
||||||
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabaseServers));
|
||||||
|
mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return container node when element is undefined.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const children = await treeDataProvider.getChildren();
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(1);
|
||||||
|
|
||||||
|
const child = children[0];
|
||||||
|
should(child.account).undefined();
|
||||||
|
should(child.subscription).undefined();
|
||||||
|
should(child.tenantId).undefined();
|
||||||
|
should(child.treeItem.id).equal('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer');
|
||||||
|
should(child.treeItem.label).equal('SQL Servers');
|
||||||
|
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||||
|
should(child.treeItem.contextValue).equal('azure.resource.itemType.databaseServerContainer');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
|
||||||
|
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||||
|
|
||||||
|
const children = await treeDataProvider.getChildren(mockResourceRootNode);
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(mockDatabaseServers.length);
|
||||||
|
|
||||||
|
for (let ix = 0; ix < children.length; ix++) {
|
||||||
|
const child = children[ix];
|
||||||
|
const databaseServer = mockDatabaseServers[ix];
|
||||||
|
|
||||||
|
should(child.account).equal(mockAccount);
|
||||||
|
should(child.subscription).equal(mockSubscription);
|
||||||
|
should(child.tenantId).equal(mockTenantId);
|
||||||
|
should(child.treeItem.id).equal(`databaseServer_${databaseServer.name}`);
|
||||||
|
should(child.treeItem.label).equal(databaseServer.name);
|
||||||
|
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||||
|
should(child.treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import 'mocha';
|
||||||
|
import { fail } from 'assert';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azureResource/azure-resource';
|
||||||
|
import { AzureResourceService } from '../../azureResource/resourceService';
|
||||||
|
|
||||||
|
// Mock test data
|
||||||
|
const mockAccount: sqlops.Account = {
|
||||||
|
key: {
|
||||||
|
accountId: 'mock_account',
|
||||||
|
providerId: 'mock_provider'
|
||||||
|
},
|
||||||
|
displayInfo: {
|
||||||
|
displayName: 'mock_account@test.com',
|
||||||
|
accountType: 'Microsoft',
|
||||||
|
contextualDisplayName: 'test'
|
||||||
|
},
|
||||||
|
properties: undefined,
|
||||||
|
isStale: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
|
id: 'mock_subscription',
|
||||||
|
name: 'mock subscription'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider1: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider1: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider2: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider2: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
const resourceService: AzureResourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
|
describe('AzureResourceService.listResourceProviderIds', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
|
||||||
|
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when registering providers.', async function(): Promise<void> {
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
let providerIds = await resourceService.listResourceProviderIds();
|
||||||
|
should(providerIds).Array();
|
||||||
|
should(providerIds.length).equal(1);
|
||||||
|
should(providerIds[0]).equal(mockResourceProvider1.object.providerId);
|
||||||
|
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider2.object);
|
||||||
|
providerIds = await resourceService.listResourceProviderIds();
|
||||||
|
should(providerIds).Array();
|
||||||
|
should(providerIds.length).equal(2);
|
||||||
|
should(providerIds[0]).equal(mockResourceProvider1.object.providerId);
|
||||||
|
should(providerIds[1]).equal(mockResourceProvider2.object.providerId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceService.getRootChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when provider id is correct.', async function(): Promise<void> {
|
||||||
|
const children = await resourceService.getRootChildren(mockResourceProvider1.object.providerId, mockAccount, mockSubscription, mockTenantId);
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
|
||||||
|
const providerId = 'non_existent_provider_id';
|
||||||
|
try {
|
||||||
|
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
|
||||||
|
} catch (error) {
|
||||||
|
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceService.getChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when provider id is correct.', async function(): Promise<void> {
|
||||||
|
const children = await resourceService.getChildren(mockResourceProvider1.object.providerId, TypeMoq.It.isAny());
|
||||||
|
should(children).Array();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
|
||||||
|
const providerId = 'non_existent_provider_id';
|
||||||
|
try {
|
||||||
|
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
|
||||||
|
} catch (error) {
|
||||||
|
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceService.getTreeItem', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when provider id is correct.', async function(): Promise<void> {
|
||||||
|
const treeItem = await resourceService.getTreeItem(mockResourceProvider1.object.providerId, TypeMoq.It.isAny());
|
||||||
|
should(treeItem).Object();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
|
||||||
|
const providerId = 'non_existent_provider_id';
|
||||||
|
try {
|
||||||
|
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
|
||||||
|
} catch (error) {
|
||||||
|
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
import { azureResource } from '../../azureResource/azure-resource';
|
||||||
|
import { AzureResourceService } from '../../azureResource/resourceService';
|
||||||
|
import { AzureResourceResourceTreeNode } from '../../azureResource/resourceTreeNode';
|
||||||
|
|
||||||
|
const resourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
|
// Mock test data
|
||||||
|
const mockAccount: sqlops.Account = {
|
||||||
|
key: {
|
||||||
|
accountId: 'mock_account',
|
||||||
|
providerId: 'mock_provider'
|
||||||
|
},
|
||||||
|
displayInfo: {
|
||||||
|
displayName: 'mock_account@test.com',
|
||||||
|
accountType: 'Microsoft',
|
||||||
|
contextualDisplayName: 'test'
|
||||||
|
},
|
||||||
|
properties: undefined,
|
||||||
|
isStale: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
|
id: 'mock_subscription',
|
||||||
|
name: 'mock subscription'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
const mockResourceProviderId: string = 'mock_resource_provider';
|
||||||
|
|
||||||
|
const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_root_node',
|
||||||
|
label: 'mock resource root node',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||||
|
contextValue: 'mock_resource_root_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockResourceNode1: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_node_1',
|
||||||
|
label: 'mock resource node 1',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.None,
|
||||||
|
contextValue: 'mock_resource_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockResourceNode2: azureResource.IAzureResourceNode = {
|
||||||
|
account: mockAccount,
|
||||||
|
subscription: mockSubscription,
|
||||||
|
tenantId: mockTenantId,
|
||||||
|
treeItem: {
|
||||||
|
id: 'mock_resource_node_2',
|
||||||
|
label: 'mock resource node 2',
|
||||||
|
iconPath: undefined,
|
||||||
|
collapsibleState: vscode.TreeItemCollapsibleState.None,
|
||||||
|
contextValue: 'mock_resource_node'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockResourceNodes: azureResource.IAzureResourceNode[] = [mockResourceNode1, mockResourceNode2];
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
describe('AzureResourceResourceTreeNode.info', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider.setup((o) => o.getTreeItem(mockResourceRootNode)).returns(() => mockResourceRootNode.treeItem);
|
||||||
|
mockResourceTreeDataProvider.setup((o) => o.getChildren(mockResourceRootNode)).returns(() => Promise.resolve(mockResourceNodes));
|
||||||
|
|
||||||
|
mockResourceProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider.setup((o) => o.providerId).returns(() => mockResourceProviderId);
|
||||||
|
mockResourceProvider.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider.object);
|
||||||
|
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
|
const resourceTreeNode = new AzureResourceResourceTreeNode({
|
||||||
|
resourceProviderId: mockResourceProviderId,
|
||||||
|
resourceNode: mockResourceRootNode
|
||||||
|
}, undefined);
|
||||||
|
|
||||||
|
should(resourceTreeNode.nodePathValue).equal(mockResourceRootNode.treeItem.id);
|
||||||
|
|
||||||
|
const treeItem = await resourceTreeNode.getTreeItem();
|
||||||
|
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
|
||||||
|
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
|
||||||
|
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
|
||||||
|
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
|
||||||
|
const nodeInfo = resourceTreeNode.getNodeInfo();
|
||||||
|
should(nodeInfo.label).equal(mockResourceRootNode.treeItem.label);
|
||||||
|
should(nodeInfo.isLeaf).equal(mockResourceRootNode.treeItem.collapsibleState === vscode.TreeItemCollapsibleState.None);
|
||||||
|
should(nodeInfo.nodeType).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
should(nodeInfo.iconType).equal(mockResourceRootNode.treeItem.contextValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('AzureResourceResourceTreeNode.getChildren', function(): void {
|
||||||
|
beforeEach(() => {
|
||||||
|
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider.setup((o) => o.getChildren(mockResourceRootNode)).returns(() => Promise.resolve(mockResourceNodes));
|
||||||
|
|
||||||
|
mockResourceProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider.setup((o) => o.providerId).returns(() => mockResourceProviderId);
|
||||||
|
mockResourceProvider.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider.object);
|
||||||
|
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
|
||||||
|
const resourceTreeNode = new AzureResourceResourceTreeNode({
|
||||||
|
resourceProviderId: mockResourceProviderId,
|
||||||
|
resourceNode: mockResourceRootNode
|
||||||
|
}, undefined);
|
||||||
|
|
||||||
|
const children = await resourceTreeNode.getChildren();
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider.verify((o) => o.getChildren(mockResourceRootNode), TypeMoq.Times.once());
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(mockResourceNodes.length);
|
||||||
|
|
||||||
|
for (let ix = 0; ix < children.length; ix++) {
|
||||||
|
const child = children[ix];
|
||||||
|
|
||||||
|
should(child).instanceOf(AzureResourceResourceTreeNode);
|
||||||
|
|
||||||
|
const childNode = (child as AzureResourceResourceTreeNode).resourceNodeWithProviderId;
|
||||||
|
should(childNode.resourceProviderId).equal(mockResourceProviderId);
|
||||||
|
should(childNode.resourceNode.account).equal(mockAccount);
|
||||||
|
should(childNode.resourceNode.subscription).equal(mockSubscription);
|
||||||
|
should(childNode.resourceNode.tenantId).equal(mockTenantId);
|
||||||
|
should(childNode.resourceNode.treeItem.id).equal(mockResourceNodes[ix].treeItem.id);
|
||||||
|
should(childNode.resourceNode.treeItem.label).equal(mockResourceNodes[ix].treeItem.label);
|
||||||
|
should(childNode.resourceNode.treeItem.collapsibleState).equal(mockResourceNodes[ix].treeItem.collapsibleState);
|
||||||
|
should(childNode.resourceNode.treeItem.contextValue).equal(mockResourceNodes[ix].treeItem.contextValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return empty when it is leaf node.', async function(): Promise<void> {
|
||||||
|
const resourceTreeNode = new AzureResourceResourceTreeNode({
|
||||||
|
resourceProviderId: mockResourceProviderId,
|
||||||
|
resourceNode: mockResourceNode1
|
||||||
|
}, undefined);
|
||||||
|
|
||||||
|
const children = await resourceTreeNode.getChildren();
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider.verify((o) => o.getChildren(), TypeMoq.Times.exactly(0));
|
||||||
|
|
||||||
|
should(children).Array();
|
||||||
|
should(children.length).equal(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -26,7 +26,7 @@ describe('AzureResourceAccountNotSignedInTreeNode.info', function(): void {
|
|||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||||
should(treeItem.command).not.undefined();
|
should(treeItem.command).not.undefined();
|
||||||
should(treeItem.command.title).equal(label);
|
should(treeItem.command.title).equal(label);
|
||||||
should(treeItem.command.command).equal('azureresource.signin');
|
should(treeItem.command.command).equal('azure.resource.signin');
|
||||||
|
|
||||||
const nodeInfo = treeNode.getNodeInfo();
|
const nodeInfo = treeNode.getNodeInfo();
|
||||||
should(nodeInfo.isLeaf).true();
|
should(nodeInfo.isLeaf).true();
|
||||||
|
|||||||
@@ -10,35 +10,38 @@ import * as TypeMoq from 'typemoq';
|
|||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
import { TokenCredentials } from 'ms-rest';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
import { azureResource } from '../../../azureResource/azure-resource';
|
||||||
import {
|
import {
|
||||||
IAzureResourceCacheService,
|
IAzureResourceCacheService,
|
||||||
IAzureResourceContextService,
|
|
||||||
IAzureResourceCredentialService,
|
|
||||||
IAzureResourceSubscriptionService,
|
IAzureResourceSubscriptionService,
|
||||||
IAzureResourceSubscriptionFilterService
|
IAzureResourceSubscriptionFilterService,
|
||||||
|
IAzureResourceTenantService
|
||||||
} from '../../../azureResource/interfaces';
|
} from '../../../azureResource/interfaces';
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
import { generateGuid } from '../../../azureResource/utils';
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
|
||||||
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
||||||
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
|
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
|
||||||
|
let mockTenantService: TypeMoq.IMock<IAzureResourceTenantService>;
|
||||||
|
let mockAppContext: AppContext;
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||||
|
|
||||||
// Mock test data
|
// Mock test data
|
||||||
|
const mockTenantId = 'mock_tenant_id';
|
||||||
|
|
||||||
const mockAccount: sqlops.Account = {
|
const mockAccount: sqlops.Account = {
|
||||||
key: {
|
key: {
|
||||||
accountId: 'mock_account',
|
accountId: 'mock_account',
|
||||||
@@ -49,51 +52,68 @@ const mockAccount: sqlops.Account = {
|
|||||||
accountType: 'Microsoft',
|
accountType: 'Microsoft',
|
||||||
contextualDisplayName: 'test'
|
contextualDisplayName: 'test'
|
||||||
},
|
},
|
||||||
properties: undefined,
|
properties: {
|
||||||
|
tenants: [
|
||||||
|
{
|
||||||
|
id: mockTenantId
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
isStale: false
|
isStale: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
const mockSubscription1: azureResource.AzureResourceSubscription = {
|
||||||
const mockCredentials = [mockCredential];
|
|
||||||
|
|
||||||
const mockSubscription1: AzureResourceSubscription = {
|
|
||||||
id: 'mock_subscription_1',
|
id: 'mock_subscription_1',
|
||||||
name: 'mock subscription 1'
|
name: 'mock subscription 1'
|
||||||
};
|
};
|
||||||
const mockSubscription2: AzureResourceSubscription = {
|
|
||||||
|
const mockSubscription2: azureResource.AzureResourceSubscription = {
|
||||||
id: 'mock_subscription_2',
|
id: 'mock_subscription_2',
|
||||||
name: 'mock subscription 2'
|
name: 'mock subscription 2'
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockSubscriptions = [mockSubscription1, mockSubscription2];
|
const mockSubscriptions = [mockSubscription1, mockSubscription2];
|
||||||
|
|
||||||
const mockFilteredSubscriptions = [mockSubscription1];
|
const mockFilteredSubscriptions = [mockSubscription1];
|
||||||
|
|
||||||
let mockSubscriptionCache: { subscriptions: { [accountId: string]: AzureResourceSubscription[]} };
|
const mockTokens = {};
|
||||||
|
mockTokens[mockTenantId] = {
|
||||||
|
token: 'mock_token',
|
||||||
|
tokenType: 'Bearer'
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockCredential = new TokenCredentials(mockTokens[mockTenantId].token, mockTokens[mockTenantId].tokenType);
|
||||||
|
|
||||||
|
let mockSubscriptionCache: azureResource.AzureResourceSubscription[] = [];
|
||||||
|
|
||||||
describe('AzureResourceAccountTreeNode.info', function(): void {
|
describe('AzureResourceAccountTreeNode.info', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||||
|
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
mockSubscriptionCache = { subscriptions: {} };
|
mockSubscriptionCache = [];
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
|
||||||
|
|
||||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache.subscriptions[mockAccount.key.accountId] = mockSubscriptions);
|
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
|
||||||
|
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
|
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
|
||||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId})`;
|
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId})`;
|
||||||
@@ -114,14 +134,17 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should be correct when there are subscriptions listed.', async function(): Promise<void> {
|
it('Should be correct when there are subscriptions listed.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
await accountTreeNode.getChildren();
|
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
|
should(subscriptionNodes).Array();
|
||||||
|
should(subscriptionNodes.length).equal(mockSubscriptions.length);
|
||||||
|
|
||||||
const treeItem = await accountTreeNode.getTreeItem();
|
const treeItem = await accountTreeNode.getTreeItem();
|
||||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||||
@@ -131,14 +154,17 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should be correct when there are subscriptions filtered.', async function(): Promise<void> {
|
it('Should be correct when there are subscriptions filtered.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||||
|
|
||||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
await accountTreeNode.getChildren();
|
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
|
should(subscriptionNodes).Array();
|
||||||
|
should(subscriptionNodes.length).equal(mockFilteredSubscriptions.length);
|
||||||
|
|
||||||
const treeItem = await accountTreeNode.getTreeItem();
|
const treeItem = await accountTreeNode.getTreeItem();
|
||||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||||
@@ -150,36 +176,41 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
|||||||
|
|
||||||
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||||
|
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
mockSubscriptionCache = { subscriptions: {} };
|
mockSubscriptionCache = [];
|
||||||
|
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
|
||||||
|
|
||||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache.subscriptions[mockAccount.key.accountId] = mockSubscriptions);
|
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
|
||||||
|
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should load subscriptions from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
it('Should load subscriptions from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(0));
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||||
|
|
||||||
@@ -192,43 +223,42 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
should(children).Array();
|
should(children).Array();
|
||||||
should(children.length).equal(mockSubscriptions.length);
|
should(children.length).equal(mockSubscriptions.length);
|
||||||
|
|
||||||
should(Object.keys(mockSubscriptionCache.subscriptions)).deepEqual([mockAccount.key.accountId]);
|
should(mockSubscriptionCache).deepEqual(mockSubscriptions);
|
||||||
should(mockSubscriptionCache.subscriptions[mockAccount.key.accountId]).deepEqual(mockSubscriptions);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
|
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
|
||||||
const child = children[ix];
|
const child = children[ix];
|
||||||
const subscription = mockSubscriptions[ix];
|
const subscription = mockSubscriptions[ix];
|
||||||
|
|
||||||
should(child).instanceof(AzureResourceSubscriptionTreeNode);
|
should(child).instanceof(AzureResourceSubscriptionTreeNode);
|
||||||
should(child.nodePathValue).equal(`subscription_${subscription.id}`);
|
should(child.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${subscription.id}.tenant_${mockTenantId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should load subscriptions from cache when it is not clearing cache.', async function(): Promise<void> {
|
it('Should load subscriptions from cache when it is not clearing cache.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
await accountTreeNode.getChildren();
|
await accountTreeNode.getChildren();
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
|
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.exactly(1));
|
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||||
|
|
||||||
should(children.length).equal(mockSubscriptionCache.subscriptions[mockAccount.key.accountId].length);
|
should(children.length).equal(mockSubscriptionCache.length);
|
||||||
|
|
||||||
for (let ix = 0; ix < mockSubscriptionCache.subscriptions[mockAccount.key.accountId].length; ix++) {
|
for (let ix = 0; ix < mockSubscriptionCache.length; ix++) {
|
||||||
should(children[ix].nodePathValue).equal(`subscription_${mockSubscriptionCache.subscriptions[mockAccount.key.accountId][ix].id}`);
|
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscriptionCache[ix].id}.tenant_${mockTenantId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should handle when there is no subscriptions.', async function(): Promise<void> {
|
it('Should handle when there is no subscriptions.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(undefined));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
@@ -242,10 +272,10 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should honor subscription filtering.', async function(): Promise<void> {
|
it('Should honor subscription filtering.', async function(): Promise<void> {
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
@@ -255,23 +285,25 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
should(children.length).equal(mockFilteredSubscriptions.length);
|
should(children.length).equal(mockFilteredSubscriptions.length);
|
||||||
|
|
||||||
for (let ix = 0; ix < mockFilteredSubscriptions.length; ix++) {
|
for (let ix = 0; ix < mockFilteredSubscriptions.length; ix++) {
|
||||||
should(children[ix].nodePathValue).equal(`subscription_${mockFilteredSubscriptions[ix].id}`);
|
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockFilteredSubscriptions[ix].id}.tenant_${mockTenantId}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should handle errors.', async function(): Promise<void> {
|
it('Should handle errors.', async function(): Promise<void> {
|
||||||
const mockError = 'Test error';
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => { throw new Error(mockError); });
|
|
||||||
|
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const mockError = 'Test error';
|
||||||
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => { throw new Error(mockError); });
|
||||||
|
|
||||||
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
|
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||||
|
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.never());
|
|
||||||
|
|
||||||
should(children).Array();
|
should(children).Array();
|
||||||
should(children.length).equal(1);
|
should(children.length).equal(1);
|
||||||
@@ -283,12 +315,33 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
|
|
||||||
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
|
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
|
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||||
|
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||||
|
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
|
mockSubscriptionCache = [];
|
||||||
|
|
||||||
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
|
||||||
|
|
||||||
|
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
|
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||||
|
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
|
||||||
|
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should clear cache.', async function(): Promise<void> {
|
it('Should clear cache.', async function(): Promise<void> {
|
||||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||||
accountTreeNode.clearCache();
|
accountTreeNode.clearCache();
|
||||||
should(accountTreeNode.isClearingCache).true();
|
should(accountTreeNode.isClearingCache).true();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import * as should from 'should';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as sqlops from 'sqlops';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import 'mocha';
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
|
||||||
import {
|
|
||||||
IAzureResourceCacheService,
|
|
||||||
IAzureResourceContextService,
|
|
||||||
IAzureResourceCredentialService,
|
|
||||||
IAzureResourceDatabaseService
|
|
||||||
} from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabase } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
|
||||||
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
|
|
||||||
import { AzureResourceDatabaseTreeNode } from '../../../azureResource/tree/databaseTreeNode';
|
|
||||||
|
|
||||||
// Mock services
|
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
|
||||||
|
|
||||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
|
||||||
let mockDatabaseService: TypeMoq.IMock<IAzureResourceDatabaseService>;
|
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
|
||||||
|
|
||||||
// Mock test data
|
|
||||||
const mockAccount: sqlops.Account = {
|
|
||||||
key: {
|
|
||||||
accountId: 'mock_account',
|
|
||||||
providerId: 'mock_provider'
|
|
||||||
},
|
|
||||||
displayInfo: {
|
|
||||||
displayName: 'mock_account@test.com',
|
|
||||||
accountType: 'Microsoft',
|
|
||||||
contextualDisplayName: 'test'
|
|
||||||
},
|
|
||||||
properties: undefined,
|
|
||||||
isStale: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
|
||||||
const mockCredentials = [mockCredential];
|
|
||||||
|
|
||||||
const mockSubscription: AzureResourceSubscription = {
|
|
||||||
id: 'mock_subscription',
|
|
||||||
name: 'mock subscription'
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockDatabase1: AzureResourceDatabase = {
|
|
||||||
name: 'mock database 1',
|
|
||||||
serverName: 'mock server 1',
|
|
||||||
serverFullName: 'mock server 1',
|
|
||||||
loginName: 'mock user 1'
|
|
||||||
};
|
|
||||||
const mockDatabase2: AzureResourceDatabase = {
|
|
||||||
name: 'mock database 2',
|
|
||||||
serverName: 'mock server 2',
|
|
||||||
serverFullName: 'mock server 2',
|
|
||||||
loginName: 'mock user 2'
|
|
||||||
};
|
|
||||||
const mockDatabases = [mockDatabase1, mockDatabase2];
|
|
||||||
|
|
||||||
let mockDatabaseContainerCache: { databases: { [subscriptionId: string]: AzureResourceDatabase[] } };
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseContainerTreeNode.info', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const databaseContainerTreeNodeLabel = 'SQL Databases';
|
|
||||||
|
|
||||||
should(databaseContainerTreeNode.nodePathValue).equal('databaseContainer');
|
|
||||||
|
|
||||||
const treeItem = await databaseContainerTreeNode.getTreeItem();
|
|
||||||
should(treeItem.label).equal(databaseContainerTreeNodeLabel);
|
|
||||||
should(treeItem.contextValue).equal(AzureResourceItemType.databaseContainer);
|
|
||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
|
||||||
|
|
||||||
const nodeInfo = databaseContainerTreeNode.getNodeInfo();
|
|
||||||
should(nodeInfo.isLeaf).false();
|
|
||||||
should(nodeInfo.label).equal(databaseContainerTreeNodeLabel);
|
|
||||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseContainer);
|
|
||||||
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseContainer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseContainerTreeNode.getChildren', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockDatabaseContainerCache = { databases: {} };
|
|
||||||
|
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
|
||||||
mockServicePool.databaseService = mockDatabaseService.object;
|
|
||||||
|
|
||||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
|
||||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockDatabaseContainerCache);
|
|
||||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockDatabaseContainerCache.databases[mockSubscription.id] = mockDatabases);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should load databases from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
|
||||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabases));
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
|
||||||
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
|
||||||
|
|
||||||
should(databaseContainerTreeNode.isClearingCache).false();
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(mockDatabases.length);
|
|
||||||
|
|
||||||
should(Object.keys(mockDatabaseContainerCache.databases)).deepEqual([mockSubscription.id]);
|
|
||||||
should(mockDatabaseContainerCache.databases[mockSubscription.id]).deepEqual(mockDatabases);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockDatabases.length; ix++) {
|
|
||||||
const child = children[ix];
|
|
||||||
const database = mockDatabases[ix];
|
|
||||||
|
|
||||||
should(child).instanceof(AzureResourceDatabaseTreeNode);
|
|
||||||
should(child.nodePathValue).equal(`database_${database.name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should load databases from cache when it is not clearing cache.', async function(): Promise<void> {
|
|
||||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabases));
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
await databaseContainerTreeNode.getChildren();
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
|
|
||||||
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.exactly(1));
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
|
||||||
|
|
||||||
should(children.length).equal(mockDatabaseContainerCache.databases[mockSubscription.id].length);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockDatabaseContainerCache.databases[mockSubscription.id].length; ix++) {
|
|
||||||
should(children[ix].nodePathValue).equal(`database_${mockDatabaseContainerCache.databases[mockSubscription.id][ix].name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should handle when there is no databases.', async function(): Promise<void> {
|
|
||||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(undefined));
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(1);
|
|
||||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
|
||||||
should(children[0].nodePathValue).startWith('message_');
|
|
||||||
should(children[0].getNodeInfo().label).equal('No SQL Databases found.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should handle errors.', async function(): Promise<void> {
|
|
||||||
const mockError = 'Test error';
|
|
||||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => { throw new Error(mockError); });
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
|
||||||
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(1);
|
|
||||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
|
||||||
should(children[0].nodePathValue).startWith('message_');
|
|
||||||
should(children[0].getNodeInfo().label).equal(`Error: ${mockError}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseContainerTreeNode.clearCache', function() : void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should clear cache.', async function(): Promise<void> {
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
databaseContainerTreeNode.clearCache();
|
|
||||||
should(databaseContainerTreeNode.isClearingCache).true();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import * as should from 'should';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as sqlops from 'sqlops';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import 'mocha';
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
|
||||||
import {
|
|
||||||
IAzureResourceCacheService,
|
|
||||||
IAzureResourceContextService,
|
|
||||||
IAzureResourceCredentialService,
|
|
||||||
IAzureResourceDatabaseServerService
|
|
||||||
} from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
|
||||||
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
|
|
||||||
import { AzureResourceDatabaseServerTreeNode } from '../../../azureResource/tree/databaseServerTreeNode';
|
|
||||||
|
|
||||||
// Mock services
|
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
|
||||||
|
|
||||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
|
||||||
let mockDatabaseServerService: TypeMoq.IMock<IAzureResourceDatabaseServerService>;
|
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
|
||||||
|
|
||||||
// Mock test data
|
|
||||||
const mockAccount: sqlops.Account = {
|
|
||||||
key: {
|
|
||||||
accountId: 'mock_account',
|
|
||||||
providerId: 'mock_provider'
|
|
||||||
},
|
|
||||||
displayInfo: {
|
|
||||||
displayName: 'mock_account@test.com',
|
|
||||||
accountType: 'Microsoft',
|
|
||||||
contextualDisplayName: 'test'
|
|
||||||
},
|
|
||||||
properties: undefined,
|
|
||||||
isStale: false
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
|
||||||
const mockCredentials = [mockCredential];
|
|
||||||
|
|
||||||
const mockSubscription: AzureResourceSubscription = {
|
|
||||||
id: 'mock_subscription',
|
|
||||||
name: 'mock subscription'
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockDatabaseServer1: AzureResourceDatabaseServer = {
|
|
||||||
name: 'mock server 1',
|
|
||||||
fullName: 'mock server 1',
|
|
||||||
loginName: 'mock user 1',
|
|
||||||
defaultDatabaseName: 'master'
|
|
||||||
};
|
|
||||||
const mockDatabaseServer2: AzureResourceDatabaseServer = {
|
|
||||||
name: 'mock server 2',
|
|
||||||
fullName: 'mock server 2',
|
|
||||||
loginName: 'mock user 2',
|
|
||||||
defaultDatabaseName: 'master'
|
|
||||||
};
|
|
||||||
const mockDatabaseServers = [mockDatabaseServer1, mockDatabaseServer2];
|
|
||||||
|
|
||||||
let mockDatabaseServerContainerCache: { databaseServers: { [subscriptionId: string]: AzureResourceDatabaseServer[] } };
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseServerContainerTreeNode.info', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const databaseServerContainerTreeNodeLabel = 'SQL Servers';
|
|
||||||
|
|
||||||
should(databaseServerContainerTreeNode.nodePathValue).equal('databaseServerContainer');
|
|
||||||
|
|
||||||
const treeItem = await databaseServerContainerTreeNode.getTreeItem();
|
|
||||||
should(treeItem.label).equal(databaseServerContainerTreeNodeLabel);
|
|
||||||
should(treeItem.contextValue).equal(AzureResourceItemType.databaseServerContainer);
|
|
||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
|
||||||
|
|
||||||
const nodeInfo = databaseServerContainerTreeNode.getNodeInfo();
|
|
||||||
should(nodeInfo.isLeaf).false();
|
|
||||||
should(nodeInfo.label).equal(databaseServerContainerTreeNodeLabel);
|
|
||||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseServerContainer);
|
|
||||||
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseServerContainer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseServerContainerTreeNode.getChildren', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockDatabaseServerContainerCache = { databaseServers: {} };
|
|
||||||
|
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
|
||||||
mockServicePool.databaseServerService = mockDatabaseServerService.object;
|
|
||||||
|
|
||||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
|
||||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockDatabaseServerContainerCache);
|
|
||||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockDatabaseServerContainerCache.databaseServers[mockSubscription.id] = mockDatabaseServers);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should load database servers from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
|
||||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabaseServers));
|
|
||||||
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const children = await databaseServerContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
|
||||||
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
|
||||||
|
|
||||||
should(databaseServerContainerTreeNode.isClearingCache).false();
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(mockDatabaseServers.length);
|
|
||||||
|
|
||||||
should(Object.keys(mockDatabaseServerContainerCache.databaseServers)).deepEqual([mockSubscription.id]);
|
|
||||||
should(mockDatabaseServerContainerCache.databaseServers[mockSubscription.id]).deepEqual(mockDatabaseServers);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockDatabaseServers.length; ix++) {
|
|
||||||
const child = children[ix];
|
|
||||||
const databaseServer = mockDatabaseServers[ix];
|
|
||||||
|
|
||||||
should(child).instanceof(AzureResourceDatabaseServerTreeNode);
|
|
||||||
should(child.nodePathValue).equal(`databaseServer_${databaseServer.name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should load database servers from cache when it is not clearing cache.', async function(): Promise<void> {
|
|
||||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabaseServers));
|
|
||||||
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
await databaseServerContainerTreeNode.getChildren();
|
|
||||||
const children = await databaseServerContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
|
|
||||||
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.exactly(1));
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
|
||||||
|
|
||||||
should(children.length).equal(mockDatabaseServerContainerCache.databaseServers[mockSubscription.id].length);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockDatabaseServerContainerCache.databaseServers[mockSubscription.id].length; ix++) {
|
|
||||||
should(children[ix].nodePathValue).equal(`databaseServer_${mockDatabaseServerContainerCache.databaseServers[mockSubscription.id][ix].name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should handle when there is no database servers.', async function(): Promise<void> {
|
|
||||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(undefined));
|
|
||||||
|
|
||||||
const databaseContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const children = await databaseContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(1);
|
|
||||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
|
||||||
should(children[0].nodePathValue).startWith('message_');
|
|
||||||
should(children[0].getNodeInfo().label).equal('No SQL Servers found.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should handle errors.', async function(): Promise<void> {
|
|
||||||
const mockError = 'Test error';
|
|
||||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => { throw new Error(mockError); });
|
|
||||||
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
const children = await databaseServerContainerTreeNode.getChildren();
|
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
|
||||||
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
|
||||||
|
|
||||||
should(children).Array();
|
|
||||||
should(children.length).equal(1);
|
|
||||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
|
||||||
should(children[0].nodePathValue).startWith('message_');
|
|
||||||
should(children[0].getNodeInfo().label).equal(`Error: ${mockError}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseServerContainerTreeNode.clearCache', function() : void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should clear cache.', async function(): Promise<void> {
|
|
||||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
|
||||||
databaseServerContainerTreeNode.clearCache();
|
|
||||||
should(databaseServerContainerTreeNode.isClearingCache).true();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import * as should from 'should';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import 'mocha';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
|
||||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
|
||||||
import { AzureResourceDatabaseServer } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
|
||||||
import { AzureResourceDatabaseServerTreeNode } from '../../../azureResource/tree/databaseServerTreeNode';
|
|
||||||
|
|
||||||
// Mock services
|
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
|
||||||
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
|
||||||
|
|
||||||
// Mock test data
|
|
||||||
const mockDatabaseServer: AzureResourceDatabaseServer = {
|
|
||||||
name: 'mock database 1',
|
|
||||||
fullName: 'mock server 1',
|
|
||||||
loginName: 'mock user 1',
|
|
||||||
defaultDatabaseName: 'master'
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseServerTreeNode.info', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
|
||||||
const databaseServerTreeNode = new AzureResourceDatabaseServerTreeNode(mockDatabaseServer, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const databaseServerTreeNodeLabel = mockDatabaseServer.name;
|
|
||||||
|
|
||||||
should(databaseServerTreeNode.nodePathValue).equal(`databaseServer_${mockDatabaseServer.name}`);
|
|
||||||
|
|
||||||
const treeItem = await databaseServerTreeNode.getTreeItem();
|
|
||||||
should(treeItem.label).equal(databaseServerTreeNodeLabel);
|
|
||||||
should(treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
|
|
||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
|
||||||
|
|
||||||
const nodeInfo = databaseServerTreeNode.getNodeInfo();
|
|
||||||
should(nodeInfo.isLeaf).true();
|
|
||||||
should(nodeInfo.label).equal(databaseServerTreeNodeLabel);
|
|
||||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseServer);
|
|
||||||
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseServer);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
import * as should from 'should';
|
|
||||||
import * as TypeMoq from 'typemoq';
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
import 'mocha';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
|
||||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
|
||||||
import { AzureResourceDatabase } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
|
||||||
import { AzureResourceDatabaseTreeNode } from '../../../azureResource/tree/databaseTreeNode';
|
|
||||||
|
|
||||||
// Mock services
|
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
|
||||||
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
|
||||||
|
|
||||||
// Mock test data
|
|
||||||
const mockDatabase: AzureResourceDatabase = {
|
|
||||||
name: 'mock database 1',
|
|
||||||
serverName: 'mock server 1',
|
|
||||||
serverFullName: 'mock server 1',
|
|
||||||
loginName: 'mock user 1'
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('AzureResourceDatabaseTreeNode.info', function(): void {
|
|
||||||
beforeEach(() => {
|
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Should be correct.', async function(): Promise<void> {
|
|
||||||
const databaseTreeNode = new AzureResourceDatabaseTreeNode(mockDatabase, mockTreeChangeHandler.object, undefined);
|
|
||||||
|
|
||||||
const databaseTreeNodeLabel = `${mockDatabase.name} (${mockDatabase.serverName})`;
|
|
||||||
|
|
||||||
should(databaseTreeNode.nodePathValue).equal(`database_${mockDatabase.name}`);
|
|
||||||
|
|
||||||
const treeItem = await databaseTreeNode.getTreeItem();
|
|
||||||
should(treeItem.label).equal(databaseTreeNodeLabel);
|
|
||||||
should(treeItem.contextValue).equal(AzureResourceItemType.database);
|
|
||||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
|
||||||
|
|
||||||
const nodeInfo = databaseTreeNode.getNodeInfo();
|
|
||||||
should(nodeInfo.isLeaf).true();
|
|
||||||
should(nodeInfo.label).equal(databaseTreeNodeLabel);
|
|
||||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.database);
|
|
||||||
should(nodeInfo.iconType).equal(AzureResourceItemType.database);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -10,20 +10,24 @@ import * as TypeMoq from 'typemoq';
|
|||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
import { azureResource } from '../../../azureResource/azure-resource';
|
||||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||||
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
|
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||||
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
|
import { AzureResourceService } from '../../../azureResource/resourceService';
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
import { AzureResourceResourceTreeNode } from '../../../azureResource/resourceTreeNode';
|
||||||
|
import { IAzureResourceCacheService } from '../../../azureResource/interfaces';
|
||||||
|
import { generateGuid } from '../../../azureResource/utils';
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
let mockAppContext: AppContext;
|
||||||
|
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
|
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||||
|
|
||||||
@@ -42,24 +46,60 @@ const mockAccount: sqlops.Account = {
|
|||||||
isStale: false
|
isStale: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockSubscription: AzureResourceSubscription = {
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
id: 'mock_subscription',
|
id: 'mock_subscription',
|
||||||
name: 'mock subscription'
|
name: 'mock subscription'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mockTenantId: string = 'mock_tenant';
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider1: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider1: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
let mockResourceTreeDataProvider2: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||||
|
let mockResourceProvider2: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||||
|
|
||||||
|
const resourceService: AzureResourceService = AzureResourceService.getInstance();
|
||||||
|
|
||||||
describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
|
|
||||||
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
|
|
||||||
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
|
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
|
||||||
|
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider2.object);
|
||||||
|
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should be correct when created.', async function(): Promise<void> {
|
it('Should be correct when created.', async function(): Promise<void> {
|
||||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
|
||||||
|
|
||||||
should(subscriptionTreeNode.nodePathValue).equal(`subscription_${mockSubscription.id}`);
|
should(subscriptionTreeNode.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscription.id}.tenant_${mockTenantId}`);
|
||||||
|
|
||||||
const treeItem = await subscriptionTreeNode.getTreeItem();
|
const treeItem = await subscriptionTreeNode.getTreeItem();
|
||||||
should(treeItem.label).equal(mockSubscription.name);
|
should(treeItem.label).equal(mockSubscription.name);
|
||||||
@@ -76,16 +116,52 @@ describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
|||||||
|
|
||||||
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
|
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
|
|
||||||
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
|
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||||
|
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||||
|
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||||
|
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||||
|
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
|
||||||
|
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
|
||||||
|
|
||||||
|
resourceService.clearResourceProviders();
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||||
|
resourceService.registerResourceProvider(mockResourceProvider2.object);
|
||||||
|
|
||||||
|
resourceService.areResourceProvidersLoaded = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should load database containers.', async function(): Promise<void> {
|
it('Should return resource containers.', async function(): Promise<void> {
|
||||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
|
||||||
const children = await subscriptionTreeNode.getChildren();
|
const children = await subscriptionTreeNode.getChildren();
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider1.verify((o) => o.getChildren(), TypeMoq.Times.once());
|
||||||
|
|
||||||
|
mockResourceTreeDataProvider2.verify((o) => o.getChildren(), TypeMoq.Times.once());
|
||||||
|
|
||||||
|
const expectedChildren = await resourceService.listResourceProviderIds();
|
||||||
|
|
||||||
should(children).Array();
|
should(children).Array();
|
||||||
should(children.length).equal(2);
|
should(children.length).equal(expectedChildren.length);
|
||||||
should(children[0]).instanceof(AzureResourceDatabaseContainerTreeNode);
|
for (const child of children) {
|
||||||
should(children[1]).instanceof(AzureResourceDatabaseServerContainerTreeNode);
|
should(child).instanceOf(AzureResourceResourceTreeNode);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,21 +5,28 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
import { IAzureResourceCacheService, IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
||||||
import { IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
|
||||||
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
|
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
|
||||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||||
import { AzureResourceAccountNotSignedInTreeNode } from '../../../azureResource/tree/accountNotSignedInTreeNode';
|
import { AzureResourceAccountNotSignedInTreeNode } from '../../../azureResource/tree/accountNotSignedInTreeNode';
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
|
||||||
|
import { AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||||
|
import { generateGuid } from '../../../azureResource/utils';
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
let mockAppContext: AppContext;
|
||||||
|
|
||||||
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
|
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||||
let mockAccountService: TypeMoq.IMock<IAzureResourceAccountService>;
|
let mockAccountService: TypeMoq.IMock<IAzureResourceAccountService>;
|
||||||
|
|
||||||
// Mock test data
|
// Mock test data
|
||||||
@@ -53,15 +60,23 @@ const mockAccounts = [mockAccount1, mockAccount2];
|
|||||||
|
|
||||||
describe('AzureResourceTreeProvider.getChildren', function(): void {
|
describe('AzureResourceTreeProvider.getChildren', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
mockAccountService = TypeMoq.Mock.ofType<IAzureResourceAccountService>();
|
mockAccountService = TypeMoq.Mock.ofType<IAzureResourceAccountService>();
|
||||||
|
|
||||||
mockServicePool.accountService = mockAccountService.object;
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
|
|
||||||
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
|
mockAppContext.registerService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService, mockAccountService.object);
|
||||||
|
|
||||||
|
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should load accounts.', async function(): Promise<void> {
|
it('Should load accounts.', async function(): Promise<void> {
|
||||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
|
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
|
||||||
|
|
||||||
const treeProvider = new AzureResourceTreeProvider();
|
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||||
treeProvider.isSystemInitialized = true;
|
treeProvider.isSystemInitialized = true;
|
||||||
|
|
||||||
const children = await treeProvider.getChildren(undefined);
|
const children = await treeProvider.getChildren(undefined);
|
||||||
@@ -83,7 +98,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
|
|||||||
it('Should handle when there is no accounts.', async function(): Promise<void> {
|
it('Should handle when there is no accounts.', async function(): Promise<void> {
|
||||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
|
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const treeProvider = new AzureResourceTreeProvider();
|
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||||
treeProvider.isSystemInitialized = true;
|
treeProvider.isSystemInitialized = true;
|
||||||
|
|
||||||
const children = await treeProvider.getChildren(undefined);
|
const children = await treeProvider.getChildren(undefined);
|
||||||
@@ -97,7 +112,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
|
|||||||
const mockAccountError = 'Test account error';
|
const mockAccountError = 'Test account error';
|
||||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
|
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
|
||||||
|
|
||||||
const treeProvider = new AzureResourceTreeProvider();
|
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||||
treeProvider.isSystemInitialized = true;
|
treeProvider.isSystemInitialized = true;
|
||||||
|
|
||||||
const children = await treeProvider.getChildren(undefined);
|
const children = await treeProvider.getChildren(undefined);
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"declaration": true
|
"declaration": false
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -67,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
|
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.15",
|
||||||
"opener": "^1.4.3",
|
"opener": "^1.4.3",
|
||||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||||
"vscode-extension-telemetry": "0.0.18",
|
"vscode-extension-telemetry": "0.0.18",
|
||||||
|
|||||||
@@ -45,4 +45,6 @@ export interface DacFxDataModel extends BaseDataModel {
|
|||||||
filePath: string;
|
filePath: string;
|
||||||
version: string;
|
version: string;
|
||||||
upgradeExisting: boolean;
|
upgradeExisting: boolean;
|
||||||
|
scriptFilePath: string;
|
||||||
|
generateScriptAndDeploy: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import * as nls from 'vscode-nls';
|
|||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import { SelectOperationPage } from './pages/selectOperationpage';
|
import { SelectOperationPage } from './pages/selectOperationpage';
|
||||||
import { DeployConfigPage } from './pages/deployConfigPage';
|
import { DeployConfigPage } from './pages/deployConfigPage';
|
||||||
|
import { DeployActionPage } from './pages/deployActionPage';
|
||||||
import { DacFxSummaryPage } from './pages/dacFxSummaryPage';
|
import { DacFxSummaryPage } from './pages/dacFxSummaryPage';
|
||||||
import { ExportConfigPage } from './pages/exportConfigPage';
|
import { ExportConfigPage } from './pages/exportConfigPage';
|
||||||
import { ExtractConfigPage } from './pages/extractConfigPage';
|
import { ExtractConfigPage } from './pages/extractConfigPage';
|
||||||
@@ -30,7 +31,33 @@ export enum Operation {
|
|||||||
deploy,
|
deploy,
|
||||||
extract,
|
extract,
|
||||||
import,
|
import,
|
||||||
export
|
export,
|
||||||
|
generateDeployScript
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DeployOperationPath {
|
||||||
|
selectOperation,
|
||||||
|
deployOptions,
|
||||||
|
deployAction,
|
||||||
|
summary
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ExtractOperationPath {
|
||||||
|
selectOperation,
|
||||||
|
options,
|
||||||
|
summary
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ImportOperationPath {
|
||||||
|
selectOperation,
|
||||||
|
options,
|
||||||
|
summary
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ExportOperationPath {
|
||||||
|
selectOperation,
|
||||||
|
options,
|
||||||
|
summary
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DataTierApplicationWizard {
|
export class DataTierApplicationWizard {
|
||||||
@@ -60,6 +87,7 @@ export class DataTierApplicationWizard {
|
|||||||
this.wizard = sqlops.window.modelviewdialog.createWizard('Data-tier Application Wizard');
|
this.wizard = sqlops.window.modelviewdialog.createWizard('Data-tier Application Wizard');
|
||||||
let selectOperationWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.selectOperationPageName', 'Select an Operation'));
|
let selectOperationWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.selectOperationPageName', 'Select an Operation'));
|
||||||
let deployConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.deployConfigPageName', 'Select Deploy Dacpac Settings'));
|
let deployConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.deployConfigPageName', 'Select Deploy Dacpac Settings'));
|
||||||
|
let deployActionWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.deployActionPageName', 'Select Action'));
|
||||||
let summaryWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.summaryPageName', 'Summary'));
|
let summaryWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.summaryPageName', 'Summary'));
|
||||||
let extractConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.extractConfigPageName', 'Select Extract Dacpac Settings'));
|
let extractConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.extractConfigPageName', 'Select Extract Dacpac Settings'));
|
||||||
let importConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.importConfigPageName', 'Select Import Bacpac Settings'));
|
let importConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.importConfigPageName', 'Select Import Bacpac Settings'));
|
||||||
@@ -67,6 +95,7 @@ export class DataTierApplicationWizard {
|
|||||||
|
|
||||||
this.pages.set('selectOperation', new Page(selectOperationWizardPage));
|
this.pages.set('selectOperation', new Page(selectOperationWizardPage));
|
||||||
this.pages.set('deployConfig', new Page(deployConfigWizardPage));
|
this.pages.set('deployConfig', new Page(deployConfigWizardPage));
|
||||||
|
this.pages.set('deployAction', new Page(deployActionWizardPage));
|
||||||
this.pages.set('extractConfig', new Page(extractConfigWizardPage));
|
this.pages.set('extractConfig', new Page(extractConfigWizardPage));
|
||||||
this.pages.set('importConfig', new Page(importConfigWizardPage));
|
this.pages.set('importConfig', new Page(importConfigWizardPage));
|
||||||
this.pages.set('exportConfig', new Page(exportConfigWizardPage));
|
this.pages.set('exportConfig', new Page(exportConfigWizardPage));
|
||||||
@@ -87,6 +116,12 @@ export class DataTierApplicationWizard {
|
|||||||
await deployConfigDacFxPage.start();
|
await deployConfigDacFxPage.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
deployActionWizardPage.registerContent(async (view) => {
|
||||||
|
let deployActionDacFxPage = new DeployActionPage(this, deployActionWizardPage, this.model, view);
|
||||||
|
this.pages.get('deployAction').dacFxPage = deployActionDacFxPage;
|
||||||
|
await deployActionDacFxPage.start();
|
||||||
|
});
|
||||||
|
|
||||||
extractConfigWizardPage.registerContent(async (view) => {
|
extractConfigWizardPage.registerContent(async (view) => {
|
||||||
let extractConfigDacFxPage = new ExtractConfigPage(this, extractConfigWizardPage, this.model, view);
|
let extractConfigDacFxPage = new ExtractConfigPage(this, extractConfigWizardPage, this.model, view);
|
||||||
this.pages.get('extractConfig').dacFxPage = extractConfigDacFxPage;
|
this.pages.get('extractConfig').dacFxPage = extractConfigDacFxPage;
|
||||||
@@ -113,39 +148,27 @@ export class DataTierApplicationWizard {
|
|||||||
|
|
||||||
this.wizard.onPageChanged(async (event) => {
|
this.wizard.onPageChanged(async (event) => {
|
||||||
let idx = event.newPage;
|
let idx = event.newPage;
|
||||||
let page: Page;
|
let page = this.getPage(idx);
|
||||||
|
|
||||||
if (idx === 1) {
|
|
||||||
switch (this.selectedOperation) {
|
|
||||||
case Operation.deploy: {
|
|
||||||
page = this.pages.get('deployConfig');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Operation.extract: {
|
|
||||||
page = this.pages.get('extractConfig');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Operation.import: {
|
|
||||||
page = this.pages.get('importConfig');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Operation.export: {
|
|
||||||
page = this.pages.get('exportConfig');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (idx === 2) {
|
|
||||||
page = this.pages.get('summary');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page !== undefined) {
|
if (page !== undefined) {
|
||||||
page.dacFxPage.setupNavigationValidator();
|
page.dacFxPage.setupNavigationValidator();
|
||||||
page.dacFxPage.onPageEnter();
|
page.dacFxPage.onPageEnter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//do onPageLeave for summary page so that GenerateScript button only shows up if upgrading database
|
||||||
|
let idxLast = event.lastPage;
|
||||||
|
|
||||||
|
if (this.isSummaryPage(idxLast)) {
|
||||||
|
let lastPage = this.pages.get('summary');
|
||||||
|
if (lastPage) {
|
||||||
|
lastPage.dacFxPage.onPageLeave();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.wizard.pages = [selectOperationWizardPage, deployConfigWizardPage, summaryWizardPage];
|
this.wizard.pages = [selectOperationWizardPage, deployConfigWizardPage, deployActionWizardPage, summaryWizardPage];
|
||||||
this.wizard.generateScriptButton.hidden = true;
|
this.wizard.generateScriptButton.hidden = true;
|
||||||
|
this.wizard.generateScriptButton.onClick(async () => await this.generateDeployScript());
|
||||||
this.wizard.doneButton.onClick(async () => await this.executeOperation());
|
this.wizard.doneButton.onClick(async () => await this.executeOperation());
|
||||||
|
|
||||||
this.wizard.open();
|
this.wizard.open();
|
||||||
@@ -177,6 +200,15 @@ export class DataTierApplicationWizard {
|
|||||||
this.selectedOperation = Operation.export;
|
this.selectedOperation = Operation.export;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Operation.generateDeployScript: {
|
||||||
|
this.wizard.doneButton.label = localize('dacFx.generateScriptButton', 'Generate Script');
|
||||||
|
this.selectedOperation = Operation.generateDeployScript;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (operation !== Operation.deploy && operation !== Operation.generateDeployScript) {
|
||||||
|
this.model.upgradeExisting = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,6 +230,10 @@ export class DataTierApplicationWizard {
|
|||||||
await this.export();
|
await this.export();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Operation.generateDeployScript: {
|
||||||
|
await this.generateDeployScript();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,6 +281,64 @@ export class DataTierApplicationWizard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async generateDeployScript() {
|
||||||
|
if (!this.model.scriptFilePath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let service = await DataTierApplicationWizard.getService(this.model.server.providerName);
|
||||||
|
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||||
|
this.wizard.message = {
|
||||||
|
text: localize('dacfx.scriptGeneratingMessage', 'You can view the status of script generation in the Task History once the wizard is closed'),
|
||||||
|
level: sqlops.window.modelviewdialog.MessageLevel.Information,
|
||||||
|
description: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = await service.generateDeployScript(this.model.filePath, this.model.database, this.model.scriptFilePath, ownerUri, sqlops.TaskExecutionMode.execute);
|
||||||
|
if (!result || !result.success) {
|
||||||
|
vscode.window.showErrorMessage(
|
||||||
|
localize('alertData.deployErrorMessage', "Deploy failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getPage(idx: number): Page {
|
||||||
|
let page: Page;
|
||||||
|
|
||||||
|
if (idx === 1) {
|
||||||
|
switch (this.selectedOperation) {
|
||||||
|
case Operation.deploy: {
|
||||||
|
page = this.pages.get('deployConfig');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Operation.extract: {
|
||||||
|
page = this.pages.get('extractConfig');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Operation.import: {
|
||||||
|
page = this.pages.get('importConfig');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Operation.export: {
|
||||||
|
page = this.pages.get('exportConfig');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ((this.selectedOperation === Operation.deploy || this.selectedOperation === Operation.generateDeployScript) && idx === DeployOperationPath.deployAction) {
|
||||||
|
page = this.pages.get('deployAction');
|
||||||
|
} else if (this.isSummaryPage(idx)) {
|
||||||
|
page = this.pages.get('summary');
|
||||||
|
}
|
||||||
|
|
||||||
|
return page;
|
||||||
|
}
|
||||||
|
|
||||||
|
private isSummaryPage(idx: number): boolean {
|
||||||
|
return this.selectedOperation === Operation.import && idx === ImportOperationPath.summary
|
||||||
|
|| this.selectedOperation === Operation.export && idx === ExportOperationPath.summary
|
||||||
|
|| this.selectedOperation === Operation.extract && idx === ExtractOperationPath.summary
|
||||||
|
|| (this.selectedOperation === Operation.deploy || this.selectedOperation === Operation.generateDeployScript) && idx === DeployOperationPath.summary;
|
||||||
|
}
|
||||||
|
|
||||||
private static async getService(providerName: string): Promise<sqlops.DacFxServicesProvider> {
|
private static async getService(providerName: string): Promise<sqlops.DacFxServicesProvider> {
|
||||||
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(providerName, sqlops.DataProviderType.DacFxServicesProvider);
|
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(providerName, sqlops.DataProviderType.DacFxServicesProvider);
|
||||||
return service;
|
return service;
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ export class DacFxSummaryPage extends BasePage {
|
|||||||
async onPageEnter(): Promise<boolean> {
|
async onPageEnter(): Promise<boolean> {
|
||||||
this.populateTable();
|
this.populateTable();
|
||||||
this.loader.loading = false;
|
this.loader.loading = false;
|
||||||
|
if (this.model.upgradeExisting && this.model.generateScriptAndDeploy) {
|
||||||
|
this.instance.wizard.generateScriptButton.hidden = false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPageLeave(): Promise<boolean> {
|
||||||
|
this.instance.wizard.generateScriptButton.hidden = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,6 +76,10 @@ export class DacFxSummaryPage extends BasePage {
|
|||||||
let sourceServer = localize('dacfx.sourceServerName', 'Source Server');
|
let sourceServer = localize('dacfx.sourceServerName', 'Source Server');
|
||||||
let sourceDatabase = localize('dacfx.sourceDatabaseName', 'Source Database');
|
let sourceDatabase = localize('dacfx.sourceDatabaseName', 'Source Database');
|
||||||
let fileLocation = localize('dacfx.fileLocation', 'File Location');
|
let fileLocation = localize('dacfx.fileLocation', 'File Location');
|
||||||
|
let scriptLocation = localize('dacfx.scriptLocation', 'Deployment Script Location');
|
||||||
|
let action = localize('dacfx.action', 'Action');
|
||||||
|
let deploy = localize('dacfx.deploy', 'Deploy');
|
||||||
|
let generateScript = localize('dacfx.generateScript', 'Generate Deployment Script');
|
||||||
|
|
||||||
switch (this.instance.selectedOperation) {
|
switch (this.instance.selectedOperation) {
|
||||||
case Operation.deploy: {
|
case Operation.deploy: {
|
||||||
@@ -75,6 +87,13 @@ export class DacFxSummaryPage extends BasePage {
|
|||||||
[targetServer, this.model.serverName],
|
[targetServer, this.model.serverName],
|
||||||
[fileLocation, this.model.filePath],
|
[fileLocation, this.model.filePath],
|
||||||
[targetDatabase, this.model.database]];
|
[targetDatabase, this.model.database]];
|
||||||
|
if (this.model.generateScriptAndDeploy) {
|
||||||
|
data[3] = [scriptLocation, this.model.scriptFilePath];
|
||||||
|
data[4] = [action, generateScript + ', ' + deploy];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data[3] = [action, deploy];
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case Operation.extract: {
|
case Operation.extract: {
|
||||||
@@ -99,12 +118,21 @@ export class DacFxSummaryPage extends BasePage {
|
|||||||
[fileLocation, this.model.filePath]];
|
[fileLocation, this.model.filePath]];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case Operation.generateDeployScript: {
|
||||||
|
data = [
|
||||||
|
[targetServer, this.model.serverName],
|
||||||
|
[fileLocation, this.model.filePath],
|
||||||
|
[targetDatabase, this.model.database],
|
||||||
|
[scriptLocation, this.model.scriptFilePath],
|
||||||
|
[action, generateScript]];
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.table.updateProperties({
|
this.table.updateProperties({
|
||||||
data: data,
|
data: data,
|
||||||
columns: ['Setting', 'Value'],
|
columns: ['Setting', 'Value'],
|
||||||
width: 600,
|
width: 700,
|
||||||
height: 200
|
height: 200
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
176
extensions/import/src/wizard/pages/deployActionPage.ts
Normal file
176
extensions/import/src/wizard/pages/deployActionPage.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as os from 'os';
|
||||||
|
import { DacFxDataModel } from '../api/models';
|
||||||
|
import { DataTierApplicationWizard, Operation } from '../dataTierApplicationWizard';
|
||||||
|
import { DacFxConfigPage } from '../api/dacFxConfigPage';
|
||||||
|
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
export class DeployActionPage extends DacFxConfigPage {
|
||||||
|
|
||||||
|
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||||
|
protected readonly instance: DataTierApplicationWizard;
|
||||||
|
protected readonly model: DacFxDataModel;
|
||||||
|
protected readonly view: sqlops.ModelView;
|
||||||
|
private deployRadioButton: sqlops.RadioButtonComponent;
|
||||||
|
private deployScriptRadioButton: sqlops.RadioButtonComponent;
|
||||||
|
private scriptRadioButton: sqlops.RadioButtonComponent;
|
||||||
|
private form: sqlops.FormContainer;
|
||||||
|
|
||||||
|
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.ModelView) {
|
||||||
|
super(instance, wizardPage, model, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<boolean> {
|
||||||
|
let deployComponent = await this.createDeployRadioButton();
|
||||||
|
let deployScriptComponent = await this.createDeployScriptRadioButton();
|
||||||
|
let scriptComponent = await this.createScriptRadioButton();
|
||||||
|
let fileBrowserComponent = await this.createFileBrowser();
|
||||||
|
|
||||||
|
this.form = this.view.modelBuilder.formContainer()
|
||||||
|
.withFormItems(
|
||||||
|
[
|
||||||
|
deployComponent,
|
||||||
|
scriptComponent,
|
||||||
|
deployScriptComponent,
|
||||||
|
fileBrowserComponent
|
||||||
|
]).component();
|
||||||
|
await this.view.initializeModel(this.form);
|
||||||
|
|
||||||
|
//default have the first radio button checked
|
||||||
|
this.deployRadioButton.checked = true;
|
||||||
|
this.toggleFileBrowser(false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPageEnter(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createDeployRadioButton(): Promise<sqlops.FormComponent> {
|
||||||
|
this.deployRadioButton = this.view.modelBuilder.radioButton()
|
||||||
|
.withProperties({
|
||||||
|
name: 'selectedDeployAction',
|
||||||
|
label: localize('dacFx.deployRadioButtonLabel', 'Deploy'),
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.deployRadioButton.onDidClick(() => {
|
||||||
|
this.model.generateScriptAndDeploy = false;
|
||||||
|
this.instance.setDoneButton(Operation.deploy);
|
||||||
|
this.toggleFileBrowser(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
component: this.deployRadioButton,
|
||||||
|
title: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createDeployScriptRadioButton(): Promise<sqlops.FormComponent> {
|
||||||
|
this.deployScriptRadioButton = this.view.modelBuilder.radioButton()
|
||||||
|
.withProperties({
|
||||||
|
name: 'selectedDeployAction',
|
||||||
|
label: localize('dacFx.deployScriptRadioButtonLabel', 'Generate Deployment Script and Deploy'),
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.deployScriptRadioButton.onDidClick(() => {
|
||||||
|
this.model.generateScriptAndDeploy = true;
|
||||||
|
this.instance.setDoneButton(Operation.deploy);
|
||||||
|
this.toggleFileBrowser(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
component: this.deployScriptRadioButton,
|
||||||
|
title: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createScriptRadioButton(): Promise<sqlops.FormComponent> {
|
||||||
|
this.scriptRadioButton = this.view.modelBuilder.radioButton()
|
||||||
|
.withProperties({
|
||||||
|
name: 'selectedDeployAction',
|
||||||
|
label: localize('dacFx.scriptRadioButtonLabel', 'Generate Deployment Script'),
|
||||||
|
}).component();
|
||||||
|
|
||||||
|
this.scriptRadioButton.onDidClick(() => {
|
||||||
|
this.model.generateScriptAndDeploy = false;
|
||||||
|
this.toggleFileBrowser(true);
|
||||||
|
|
||||||
|
//change button text and operation
|
||||||
|
this.instance.setDoneButton(Operation.generateDeployScript);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
component: this.scriptRadioButton,
|
||||||
|
title: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createFileBrowser(): Promise<sqlops.FormComponentGroup> {
|
||||||
|
this.createFileBrowserParts();
|
||||||
|
|
||||||
|
//default filepath
|
||||||
|
let now = new Date();
|
||||||
|
let datetime = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + '-' + now.getHours() + '-' + now.getMinutes();
|
||||||
|
this.fileTextBox.value= path.join(os.homedir(), this.model.database + '_UpgradeDACScript_' + datetime + '.sql');
|
||||||
|
this.model.scriptFilePath = this.fileTextBox.value;
|
||||||
|
|
||||||
|
this.fileButton.onDidClick(async (click) => {
|
||||||
|
let fileUri = await vscode.window.showSaveDialog(
|
||||||
|
{
|
||||||
|
defaultUri: vscode.Uri.file(this.fileTextBox.value),
|
||||||
|
saveLabel: localize('dacfxDeployScript.saveFile', 'Save'),
|
||||||
|
filters: {
|
||||||
|
'SQL Files': ['sql'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fileUri) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fileTextBox.value = fileUri.fsPath;
|
||||||
|
this.model.scriptFilePath = fileUri.fsPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fileTextBox.onTextChanged(async () => {
|
||||||
|
this.model.scriptFilePath = this.fileTextBox.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: '',
|
||||||
|
components: [
|
||||||
|
{
|
||||||
|
title: localize('dacfx.generatedScriptLocation','Deployment Script Location'),
|
||||||
|
component: this.fileTextBox,
|
||||||
|
layout: {
|
||||||
|
horizontal: true,
|
||||||
|
componentWidth: 400
|
||||||
|
},
|
||||||
|
actions: [this.fileButton]
|
||||||
|
},],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleFileBrowser(enable: boolean): void {
|
||||||
|
this.fileTextBox.enabled = enable;
|
||||||
|
this.fileButton.enabled = enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setupNavigationValidator() {
|
||||||
|
this.instance.registerNavigationValidator(() => {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ export class DeployConfigPage extends DacFxConfigPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<boolean> {
|
async start(): Promise<boolean> {
|
||||||
let serverComponent = await this.createServerDropdown(true);
|
let serverComponent = await this.createServerDropdown(true);
|
||||||
let fileBrowserComponent = await this.createFileBrowser();
|
let fileBrowserComponent = await this.createFileBrowser();
|
||||||
this.databaseComponent = await this.createDatabaseTextBox();
|
this.databaseComponent = await this.createDatabaseTextBox();
|
||||||
this.databaseComponent.title = localize('dacFx.databaseNameTextBox', 'Database Name');
|
this.databaseComponent.title = localize('dacFx.databaseNameTextBox', 'Database Name');
|
||||||
@@ -131,7 +131,7 @@ export class DeployConfigPage extends DacFxConfigPage {
|
|||||||
this.model.database = this.databaseTextBox.value;
|
this.model.database = this.databaseTextBox.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize with upgrade existing true
|
//Initialize with upgrade existing true
|
||||||
upgradeRadioButton.checked = true;
|
upgradeRadioButton.checked = true;
|
||||||
this.model.upgradeExisting = true;
|
this.model.upgradeExisting = true;
|
||||||
|
|
||||||
@@ -149,10 +149,10 @@ export class DeployConfigPage extends DacFxConfigPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async createDeployDatabaseDropdown(): Promise<sqlops.FormComponent> {
|
protected async createDeployDatabaseDropdown(): Promise<sqlops.FormComponent> {
|
||||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||||
required: true
|
required: true
|
||||||
}).component();
|
}).component();
|
||||||
// Handle database changes
|
//Handle database changes
|
||||||
this.databaseDropdown.onValueChanged(async () => {
|
this.databaseDropdown.onValueChanged(async () => {
|
||||||
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||||
});
|
});
|
||||||
@@ -172,7 +172,8 @@ export class DeployConfigPage extends DacFxConfigPage {
|
|||||||
}
|
}
|
||||||
let values = await this.getDatabaseValues();
|
let values = await this.getDatabaseValues();
|
||||||
|
|
||||||
if (this.model.database === undefined) {
|
//set the database to the first dropdown value if upgrading, otherwise it should get set to the textbox value
|
||||||
|
if (this.model.upgradeExisting) {
|
||||||
this.model.database = values[0].name;
|
this.model.database = values[0].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,11 +58,6 @@ export class SelectOperationPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async onPageEnter(): Promise<boolean> {
|
async onPageEnter(): Promise<boolean> {
|
||||||
let numPages = this.instance.wizard.pages.length;
|
|
||||||
for (let i = numPages - 1; i > 2; --i) {
|
|
||||||
await this.instance.wizard.removePage(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,12 +69,14 @@ export class SelectOperationPage extends BasePage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.deployRadioButton.onDidClick(() => {
|
this.deployRadioButton.onDidClick(() => {
|
||||||
// remove the previous page
|
this.removePages();
|
||||||
this.instance.wizard.removePage(1);
|
|
||||||
|
|
||||||
// add deploy page
|
//add deploy pages
|
||||||
let page = this.instance.pages.get('deployConfig');
|
let configPage = this.instance.pages.get('deployConfig');
|
||||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
this.instance.wizard.addPage(configPage.wizardPage, 1);
|
||||||
|
let actionPage = this.instance.pages.get('deployAction');
|
||||||
|
this.instance.wizard.addPage(actionPage.wizardPage, 2);
|
||||||
|
this.addSummaryPage(3);
|
||||||
|
|
||||||
// change button text and operation
|
// change button text and operation
|
||||||
this.instance.setDoneButton(Operation.deploy);
|
this.instance.setDoneButton(Operation.deploy);
|
||||||
@@ -99,12 +96,12 @@ export class SelectOperationPage extends BasePage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.extractRadioButton.onDidClick(() => {
|
this.extractRadioButton.onDidClick(() => {
|
||||||
// remove the previous pages
|
this.removePages();
|
||||||
this.instance.wizard.removePage(1);
|
|
||||||
|
|
||||||
// add the extract page
|
// add the extract page
|
||||||
let page = this.instance.pages.get('extractConfig');
|
let page = this.instance.pages.get('extractConfig');
|
||||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||||
|
this.addSummaryPage(2);
|
||||||
|
|
||||||
// change button text and operation
|
// change button text and operation
|
||||||
this.instance.setDoneButton(Operation.extract);
|
this.instance.setDoneButton(Operation.extract);
|
||||||
@@ -124,12 +121,12 @@ export class SelectOperationPage extends BasePage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.importRadioButton.onDidClick(() => {
|
this.importRadioButton.onDidClick(() => {
|
||||||
// remove the previous page
|
this.removePages();
|
||||||
this.instance.wizard.removePage(1);
|
|
||||||
|
|
||||||
// add the import page
|
// add the import page
|
||||||
let page = this.instance.pages.get('importConfig');
|
let page = this.instance.pages.get('importConfig');
|
||||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||||
|
this.addSummaryPage(2);
|
||||||
|
|
||||||
// change button text and operation
|
// change button text and operation
|
||||||
this.instance.setDoneButton(Operation.import);
|
this.instance.setDoneButton(Operation.import);
|
||||||
@@ -149,12 +146,12 @@ export class SelectOperationPage extends BasePage {
|
|||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.exportRadioButton.onDidClick(() => {
|
this.exportRadioButton.onDidClick(() => {
|
||||||
// remove the 2 previous pages
|
this.removePages();
|
||||||
this.instance.wizard.removePage(1);
|
|
||||||
|
|
||||||
// add the export pages
|
// add the export pages
|
||||||
let page = this.instance.pages.get('exportConfig');
|
let page = this.instance.pages.get('exportConfig');
|
||||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||||
|
this.addSummaryPage(2);
|
||||||
|
|
||||||
// change button text and operation
|
// change button text and operation
|
||||||
this.instance.setDoneButton(Operation.export);
|
this.instance.setDoneButton(Operation.export);
|
||||||
@@ -166,6 +163,18 @@ export class SelectOperationPage extends BasePage {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private removePages() {
|
||||||
|
let numPages = this.instance.wizard.pages.length;
|
||||||
|
for (let i = numPages - 1; i > 0; --i) {
|
||||||
|
this.instance.wizard.removePage(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private addSummaryPage(index: number) {
|
||||||
|
let summaryPage = this.instance.pages.get('summary');
|
||||||
|
this.instance.wizard.addPage(summaryPage.wizardPage, index);
|
||||||
|
}
|
||||||
|
|
||||||
public setupNavigationValidator() {
|
public setupNavigationValidator() {
|
||||||
this.instance.registerNavigationValidator(() => {
|
this.instance.registerNavigationValidator(() => {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"declaration": true
|
"declaration": false
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|||||||
@@ -5,14 +5,12 @@
|
|||||||
agent-base@4, agent-base@^4.1.0:
|
agent-base@4, agent-base@^4.1.0:
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||||
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-promisify "^5.0.0"
|
es6-promisify "^5.0.0"
|
||||||
|
|
||||||
applicationinsights@1.0.1:
|
applicationinsights@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
||||||
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
diagnostic-channel "0.2.0"
|
diagnostic-channel "0.2.0"
|
||||||
diagnostic-channel-publishers "0.2.1"
|
diagnostic-channel-publishers "0.2.1"
|
||||||
@@ -21,12 +19,10 @@ applicationinsights@1.0.1:
|
|||||||
base64-js@0.0.8:
|
base64-js@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||||
integrity sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=
|
|
||||||
|
|
||||||
bl@^1.0.0:
|
bl@^1.0.0:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||||
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
readable-stream "^2.3.5"
|
readable-stream "^2.3.5"
|
||||||
safe-buffer "^5.1.1"
|
safe-buffer "^5.1.1"
|
||||||
@@ -34,12 +30,10 @@ bl@^1.0.0:
|
|||||||
buffer-alloc-unsafe@^1.1.0:
|
buffer-alloc-unsafe@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||||
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
|
|
||||||
|
|
||||||
buffer-alloc@^1.2.0:
|
buffer-alloc@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer-alloc-unsafe "^1.1.0"
|
buffer-alloc-unsafe "^1.1.0"
|
||||||
buffer-fill "^1.0.0"
|
buffer-fill "^1.0.0"
|
||||||
@@ -47,17 +41,14 @@ buffer-alloc@^1.2.0:
|
|||||||
buffer-crc32@~0.2.3:
|
buffer-crc32@~0.2.3:
|
||||||
version "0.2.13"
|
version "0.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
|
||||||
|
|
||||||
buffer-fill@^1.0.0:
|
buffer-fill@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||||
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
|
|
||||||
|
|
||||||
buffer@^3.0.1:
|
buffer@^3.0.1:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
||||||
integrity sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
base64-js "0.0.8"
|
base64-js "0.0.8"
|
||||||
ieee754 "^1.1.4"
|
ieee754 "^1.1.4"
|
||||||
@@ -66,39 +57,34 @@ buffer@^3.0.1:
|
|||||||
commander@~2.8.1:
|
commander@~2.8.1:
|
||||||
version "2.8.1"
|
version "2.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||||
integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-readlink ">= 1.0.0"
|
graceful-readlink ">= 1.0.0"
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
core-util-is@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
|
||||||
|
|
||||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.10":
|
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.15":
|
||||||
version "0.2.10"
|
version "0.2.15"
|
||||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
|
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a2cd2db109de882f0959f7b6421c86afa585f460"
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-languageclient "3.5.1"
|
vscode-languageclient "3.5.1"
|
||||||
|
|
||||||
debug@3.1.0:
|
debug@3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@^3.1.0:
|
debug@^3.1.0:
|
||||||
version "3.2.6"
|
version "3.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||||
integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
file-type "^5.2.0"
|
file-type "^5.2.0"
|
||||||
is-stream "^1.1.0"
|
is-stream "^1.1.0"
|
||||||
@@ -107,7 +93,6 @@ decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
|||||||
decompress-tarbz2@^4.0.0:
|
decompress-tarbz2@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
||||||
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-tar "^4.1.0"
|
decompress-tar "^4.1.0"
|
||||||
file-type "^6.1.0"
|
file-type "^6.1.0"
|
||||||
@@ -118,7 +103,6 @@ decompress-tarbz2@^4.0.0:
|
|||||||
decompress-targz@^4.0.0:
|
decompress-targz@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
||||||
integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-tar "^4.1.1"
|
decompress-tar "^4.1.1"
|
||||||
file-type "^5.2.0"
|
file-type "^5.2.0"
|
||||||
@@ -127,7 +111,6 @@ decompress-targz@^4.0.0:
|
|||||||
decompress-unzip@^4.0.1:
|
decompress-unzip@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
||||||
integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
file-type "^3.8.0"
|
file-type "^3.8.0"
|
||||||
get-stream "^2.2.0"
|
get-stream "^2.2.0"
|
||||||
@@ -137,7 +120,6 @@ decompress-unzip@^4.0.1:
|
|||||||
decompress@^4.2.0:
|
decompress@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
||||||
integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-tar "^4.0.0"
|
decompress-tar "^4.0.0"
|
||||||
decompress-tarbz2 "^4.0.0"
|
decompress-tarbz2 "^4.0.0"
|
||||||
@@ -151,70 +133,58 @@ decompress@^4.2.0:
|
|||||||
diagnostic-channel-publishers@0.2.1:
|
diagnostic-channel-publishers@0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
||||||
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
|
|
||||||
|
|
||||||
diagnostic-channel@0.2.0:
|
diagnostic-channel@0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
||||||
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.3.0"
|
semver "^5.3.0"
|
||||||
|
|
||||||
end-of-stream@^1.0.0:
|
end-of-stream@^1.0.0:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
||||||
integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
once "^1.4.0"
|
once "^1.4.0"
|
||||||
|
|
||||||
es6-promise@^4.0.3:
|
es6-promise@^4.0.3:
|
||||||
version "4.2.5"
|
version "4.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
|
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
|
||||||
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
|
|
||||||
|
|
||||||
es6-promisify@^5.0.0:
|
es6-promisify@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
||||||
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-promise "^4.0.3"
|
es6-promise "^4.0.3"
|
||||||
|
|
||||||
eventemitter2@^5.0.1:
|
eventemitter2@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
||||||
integrity sha1-YZegldX7a1folC9v1+qtY6CclFI=
|
|
||||||
|
|
||||||
fd-slicer@~1.1.0:
|
fd-slicer@~1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pend "~1.2.0"
|
pend "~1.2.0"
|
||||||
|
|
||||||
file-type@^3.8.0:
|
file-type@^3.8.0:
|
||||||
version "3.9.0"
|
version "3.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||||
integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
|
|
||||||
|
|
||||||
file-type@^5.2.0:
|
file-type@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
||||||
integrity sha1-LdvqfHP/42No365J3DOMBYwritY=
|
|
||||||
|
|
||||||
file-type@^6.1.0:
|
file-type@^6.1.0:
|
||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||||
integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
|
|
||||||
|
|
||||||
fs-constants@^1.0.0:
|
fs-constants@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
|
||||||
|
|
||||||
get-stream@^2.2.0:
|
get-stream@^2.2.0:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||||
integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
pinkie-promise "^2.0.0"
|
pinkie-promise "^2.0.0"
|
||||||
@@ -222,17 +192,14 @@ get-stream@^2.2.0:
|
|||||||
graceful-fs@^4.1.10:
|
graceful-fs@^4.1.10:
|
||||||
version "4.1.15"
|
version "4.1.15"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
|
||||||
|
|
||||||
"graceful-readlink@>= 1.0.0":
|
"graceful-readlink@>= 1.0.0":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||||
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
|
|
||||||
|
|
||||||
http-proxy-agent@^2.1.0:
|
http-proxy-agent@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||||
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base "4"
|
agent-base "4"
|
||||||
debug "3.1.0"
|
debug "3.1.0"
|
||||||
@@ -240,7 +207,6 @@ http-proxy-agent@^2.1.0:
|
|||||||
https-proxy-agent@^2.2.1:
|
https-proxy-agent@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||||
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base "^4.1.0"
|
agent-base "^4.1.0"
|
||||||
debug "^3.1.0"
|
debug "^3.1.0"
|
||||||
@@ -248,115 +214,94 @@ https-proxy-agent@^2.2.1:
|
|||||||
ieee754@^1.1.4:
|
ieee754@^1.1.4:
|
||||||
version "1.1.12"
|
version "1.1.12"
|
||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||||
integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==
|
|
||||||
|
|
||||||
inherits@~2.0.3:
|
inherits@~2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
|
||||||
|
|
||||||
is-natural-number@^4.0.1:
|
is-natural-number@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
||||||
integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
|
|
||||||
|
|
||||||
is-stream@^1.1.0:
|
is-stream@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
|
||||||
|
|
||||||
isarray@^1.0.0, isarray@~1.0.0:
|
isarray@^1.0.0, isarray@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
|
||||||
|
|
||||||
make-dir@^1.0.0:
|
make-dir@^1.0.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||||
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pify "^3.0.0"
|
pify "^3.0.0"
|
||||||
|
|
||||||
minimist@0.0.8:
|
minimist@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
|
||||||
|
|
||||||
mkdirp@^0.5.1:
|
mkdirp@^0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||||
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
|
||||||
|
|
||||||
ms@^2.1.1:
|
ms@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
|
||||||
|
|
||||||
object-assign@^4.0.1:
|
object-assign@^4.0.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
|
||||||
|
|
||||||
once@^1.4.0:
|
once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
opener@^1.4.3:
|
opener@^1.4.3:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
||||||
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
|
|
||||||
|
|
||||||
os-tmpdir@~1.0.2:
|
os-tmpdir@~1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||||
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
|
|
||||||
|
|
||||||
pend@~1.2.0:
|
pend@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
|
||||||
|
|
||||||
pify@^2.3.0:
|
pify@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
|
|
||||||
|
|
||||||
pify@^3.0.0:
|
pify@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||||
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
|
|
||||||
|
|
||||||
pinkie-promise@^2.0.0:
|
pinkie-promise@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||||
integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pinkie "^2.0.0"
|
pinkie "^2.0.0"
|
||||||
|
|
||||||
pinkie@^2.0.0:
|
pinkie@^2.0.0:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||||
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
|
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||||
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
|
||||||
|
|
||||||
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||||
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
core-util-is "~1.0.0"
|
core-util-is "~1.0.0"
|
||||||
inherits "~2.0.3"
|
inherits "~2.0.3"
|
||||||
@@ -369,19 +314,16 @@ readable-stream@^2.3.0, readable-stream@^2.3.5:
|
|||||||
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
|
||||||
|
|
||||||
seek-bzip@^1.0.5:
|
seek-bzip@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
||||||
integrity sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
commander "~2.8.1"
|
commander "~2.8.1"
|
||||||
|
|
||||||
semver@^5.3.0:
|
semver@^5.3.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||||
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
|
|
||||||
|
|
||||||
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
||||||
version "0.1.5"
|
version "0.1.5"
|
||||||
@@ -397,21 +339,18 @@ semver@^5.3.0:
|
|||||||
string_decoder@~1.1.1:
|
string_decoder@~1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
strip-dirs@^2.0.0:
|
strip-dirs@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
||||||
integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
is-natural-number "^4.0.1"
|
is-natural-number "^4.0.1"
|
||||||
|
|
||||||
tar-stream@^1.5.2:
|
tar-stream@^1.5.2:
|
||||||
version "1.6.2"
|
version "1.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
bl "^1.0.0"
|
bl "^1.0.0"
|
||||||
buffer-alloc "^1.2.0"
|
buffer-alloc "^1.2.0"
|
||||||
@@ -424,24 +363,20 @@ tar-stream@^1.5.2:
|
|||||||
through@^2.3.6:
|
through@^2.3.6:
|
||||||
version "2.3.8"
|
version "2.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
|
||||||
|
|
||||||
tmp@^0.0.33:
|
tmp@^0.0.33:
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||||
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
os-tmpdir "~1.0.2"
|
os-tmpdir "~1.0.2"
|
||||||
|
|
||||||
to-buffer@^1.1.1:
|
to-buffer@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||||
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
|
|
||||||
|
|
||||||
unbzip2-stream@^1.0.9:
|
unbzip2-stream@^1.0.9:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
||||||
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer "^3.0.1"
|
buffer "^3.0.1"
|
||||||
through "^2.3.6"
|
through "^2.3.6"
|
||||||
@@ -449,31 +384,26 @@ unbzip2-stream@^1.0.9:
|
|||||||
util-deprecate@~1.0.1:
|
util-deprecate@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
|
||||||
|
|
||||||
vscode-extension-telemetry@0.0.18:
|
vscode-extension-telemetry@0.0.18:
|
||||||
version "0.0.18"
|
version "0.0.18"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
|
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
|
||||||
integrity sha512-Vw3Sr+dZwl+c6PlsUwrTtCOJkgrmvS3OUVDQGcmpXWAgq9xGq6as0K4pUx+aGqTjzLAESmWSrs6HlJm6J6Khcg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
applicationinsights "1.0.1"
|
applicationinsights "1.0.1"
|
||||||
|
|
||||||
vscode-jsonrpc@3.5.0:
|
vscode-jsonrpc@3.5.0:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
||||||
integrity sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=
|
|
||||||
|
|
||||||
vscode-languageclient@3.5.1:
|
vscode-languageclient@3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
||||||
integrity sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-languageserver-protocol "3.5.1"
|
vscode-languageserver-protocol "3.5.1"
|
||||||
|
|
||||||
vscode-languageserver-protocol@3.5.1:
|
vscode-languageserver-protocol@3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
||||||
integrity sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-jsonrpc "3.5.0"
|
vscode-jsonrpc "3.5.0"
|
||||||
vscode-languageserver-types "3.5.0"
|
vscode-languageserver-types "3.5.0"
|
||||||
@@ -481,27 +411,22 @@ vscode-languageserver-protocol@3.5.1:
|
|||||||
vscode-languageserver-types@3.5.0:
|
vscode-languageserver-types@3.5.0:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
||||||
integrity sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=
|
|
||||||
|
|
||||||
vscode-nls@^3.2.1:
|
vscode-nls@^3.2.1:
|
||||||
version "3.2.5"
|
version "3.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
|
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
|
||||||
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
|
|
||||||
|
|
||||||
wrappy@1:
|
wrappy@1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
|
||||||
|
|
||||||
xtend@^4.0.0:
|
xtend@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||||
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
|
||||||
|
|
||||||
yauzl@^2.4.2:
|
yauzl@^2.4.2:
|
||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer-crc32 "~0.2.3"
|
buffer-crc32 "~0.2.3"
|
||||||
fd-slicer "~1.1.0"
|
fd-slicer "~1.1.0"
|
||||||
@@ -509,4 +434,3 @@ yauzl@^2.4.2:
|
|||||||
zone.js@0.7.6:
|
zone.js@0.7.6:
|
||||||
version "0.7.6"
|
version "0.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
|
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
|
||||||
integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=
|
|
||||||
|
|||||||
17
extensions/integration-tests/.vscode/launch.json
vendored
Normal file
17
extensions/integration-tests/.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Tests",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"runtimeExecutable": "${execPath}",
|
||||||
|
"args": ["${workspaceFolder}/../../", "${workspaceFolder}/test", "--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out" ],
|
||||||
|
"stopOnEntry": false,
|
||||||
|
"sourceMaps": true,
|
||||||
|
"outDir": "${workspaceFolder}/out",
|
||||||
|
"preLaunchTask": "npm"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
31
extensions/integration-tests/.vscode/tasks.json
vendored
Normal file
31
extensions/integration-tests/.vscode/tasks.json
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Available variables which can be used inside of strings.
|
||||||
|
// ${workspaceFolder}: the root folder of the team
|
||||||
|
// ${file}: the current opened file
|
||||||
|
// ${relativeFile}: the current opened file relative to cwd
|
||||||
|
// ${fileBasename}: the current opened file's basename
|
||||||
|
// ${fileDirname}: the current opened file's dirname
|
||||||
|
// ${fileExtname}: the current opened file's extension
|
||||||
|
// ${cwd}: the current working directory of the spawned process
|
||||||
|
|
||||||
|
// A task runner that calls a custom npm script that compiles the extension.
|
||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
|
||||||
|
// we want to run npm
|
||||||
|
"command": "npm",
|
||||||
|
|
||||||
|
// the command is a shell script
|
||||||
|
"isShellCommand": true,
|
||||||
|
|
||||||
|
// show the output window only if unrecognized errors occur.
|
||||||
|
"showOutput": "silent",
|
||||||
|
|
||||||
|
// we run the custom script "compile" as defined in package.json
|
||||||
|
"args": ["run", "compile", "--loglevel", "silent"],
|
||||||
|
|
||||||
|
// The tsc compiler is started in watching mode
|
||||||
|
"isWatching": true,
|
||||||
|
|
||||||
|
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
||||||
|
"problemMatcher": "$tsc-watch"
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
copy the extension installers to this folder
|
||||||
2549
extensions/integration-tests/package-lock.json
generated
Normal file
2549
extensions/integration-tests/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
58
extensions/integration-tests/package.json
Normal file
58
extensions/integration-tests/package.json
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
{
|
||||||
|
"name": "integration-tests",
|
||||||
|
"description": "Integration Tests",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"publisher": "Microsoft",
|
||||||
|
"private": true,
|
||||||
|
"engines": {
|
||||||
|
"vscode": "*",
|
||||||
|
"sqlops": "*"
|
||||||
|
},
|
||||||
|
"activationEvents": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"main": "./out/main",
|
||||||
|
"extensionDependencies": [
|
||||||
|
"Microsoft.agent",
|
||||||
|
"Microsoft.import",
|
||||||
|
"Microsoft.profiler",
|
||||||
|
"Microsoft.mssql",
|
||||||
|
"Microsoft.notebook"
|
||||||
|
],
|
||||||
|
"contributes": {
|
||||||
|
"configuration": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "ADS Integration Test Configuration",
|
||||||
|
"properties": {
|
||||||
|
"test.testSetupCompleted": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commands": [{
|
||||||
|
"command": "test.setupIntegrationTest",
|
||||||
|
"title": "Setup Integration Test",
|
||||||
|
"category": "Test"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "test.waitForExtensionsToLoad",
|
||||||
|
"title": "Wait For Extensions To Load",
|
||||||
|
"category": "Test"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "7.0.43",
|
||||||
|
"@types/chai": "3.4.34",
|
||||||
|
"mocha-junit-reporter": "^1.17.0",
|
||||||
|
"mocha-multi-reporters": "^1.1.7",
|
||||||
|
"vscode": "1.1.5",
|
||||||
|
"chai": "3.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
extensions/integration-tests/readme.md
Normal file
32
extensions/integration-tests/readme.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
This integration-tests suite is based on the extension testing feature provided by VS Code, We can use this for:
|
||||||
|
a. Commands for setting up the environment for feature testing.
|
||||||
|
b. Adding test cases that do not need UI interaction or the test scenarios not supported by the UI automation framework (e.g. object explorer context menu – not html based)
|
||||||
|
|
||||||
|
extensionInstallers folder: copy the VISX installers for the extensions we would like to run the tests with.
|
||||||
|
src folder: this is where the test file for features should be added, name the file like this: feature.test.ts. e.g. objectExplorer.test.ts
|
||||||
|
|
||||||
|
UI automation testing:
|
||||||
|
the ADS UI automation test cases should be added under $root/test/smoke/src/sql folder. Each feature should create its own folder and add 2 files, one for accessing the feature and the other for the test cases. For example: objectExplorer.ts and objectExplorer.test.ts.
|
||||||
|
|
||||||
|
Setup step:
|
||||||
|
1. Launch ADS
|
||||||
|
2. Install extensions from /extensions/integration-tests/extensionInstallers by calling the test command in the integration-tests extension
|
||||||
|
3. Set configuration values. E.g. Enable preview features by calling the test command in the integration-tests extension
|
||||||
|
|
||||||
|
For now this has only been tested for Windows platform
|
||||||
|
|
||||||
|
How to run the test:
|
||||||
|
1. In the build pipeline:
|
||||||
|
The integration tests and UI automation tests have been added to ADS windows pipeline to run the test and report the results, you can find the test result under the test tab.
|
||||||
|
|
||||||
|
2. Local environment:
|
||||||
|
Integration tests:
|
||||||
|
test-integration.bat or test-integration.sh under scripts folder
|
||||||
|
|
||||||
|
UI automation tests:
|
||||||
|
navigate to test/smoke folder and run: node test/index.js
|
||||||
|
You can also run UI automation from VSCode by selecting the launch option: Launch Smoke Test.
|
||||||
|
|
||||||
|
ADS will be launched using new temp folders: extension folder and data folder so that your local dev environment won't be changed.
|
||||||
|
|
||||||
|
|
||||||
36
extensions/integration-tests/src/index.ts
Normal file
36
extensions/integration-tests/src/index.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { context } from './testContext';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
const testRunner = require('vscode/lib/testrunner');
|
||||||
|
|
||||||
|
const suite = 'Integration Tests';
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
ui: 'tdd',
|
||||||
|
useColors: true,
|
||||||
|
timeout: 600000
|
||||||
|
};
|
||||||
|
|
||||||
|
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||||
|
options.reporter = 'mocha-multi-reporters';
|
||||||
|
options.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`)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vscode.workspace.getConfiguration('test')['testSetupCompleted']) {
|
||||||
|
context.RunTest = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
testRunner.configure(options);
|
||||||
|
|
||||||
|
export = testRunner;
|
||||||
77
extensions/integration-tests/src/main.ts
Normal file
77
extensions/integration-tests/src/main.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
'use strict';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import { normalize, join } from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
const TEST_SETUP_COMPLETED_TEXT: string = 'Test Setup Completed';
|
||||||
|
const EXTENSION_LOADED_TEXT: string = 'Test Extension Loaded';
|
||||||
|
const ALL_EXTENSION_LOADED_TEXT: string = 'All Extensions Loaded';
|
||||||
|
|
||||||
|
var statusBarItemTimer: NodeJS.Timer;
|
||||||
|
|
||||||
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
var statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||||
|
vscode.commands.registerCommand('test.setupIntegrationTest', async () => {
|
||||||
|
let extensionInstallersFolder = normalize(join(__dirname, '../extensionInstallers'));
|
||||||
|
let installers = fs.readdirSync(extensionInstallersFolder);
|
||||||
|
for (let i = 0; i < installers.length; i++) {
|
||||||
|
if (installers[i].endsWith('.vsix')) {
|
||||||
|
let installerFullPath = join(extensionInstallersFolder, installers[i]);
|
||||||
|
await sqlops.extensions.install(installerFullPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await setConfiguration('workbench.enablePreviewFeatures', true);
|
||||||
|
await setConfiguration('workbench.showConnectDialogOnStartup', false);
|
||||||
|
await setConfiguration('test.testSetupCompleted', true);
|
||||||
|
showStatusBarItem(statusBarItem, TEST_SETUP_COMPLETED_TEXT);
|
||||||
|
});
|
||||||
|
|
||||||
|
vscode.commands.registerCommand('test.waitForExtensionsToLoad', async () => {
|
||||||
|
let expectedExtensions = ['Microsoft.agent', 'Microsoft.import', 'Microsoft.mssql', 'Microsoft.profiler'];
|
||||||
|
do {
|
||||||
|
let extensions = vscode.extensions.all.filter(ext => { return expectedExtensions.indexOf(ext.id) !== -1; });
|
||||||
|
|
||||||
|
let isReady = true;
|
||||||
|
for (let i = 0; i < extensions.length; i++) {
|
||||||
|
let extension = extensions[i];
|
||||||
|
isReady = isReady && extension.isActive;
|
||||||
|
if (!isReady) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReady) {
|
||||||
|
showStatusBarItem(statusBarItem, ALL_EXTENSION_LOADED_TEXT);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
await new Promise(resolve => { setTimeout(resolve, 1000); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (true);
|
||||||
|
});
|
||||||
|
showStatusBarItem(statusBarItem, EXTENSION_LOADED_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showStatusBarItem(statusBarItem: vscode.StatusBarItem, text: string) {
|
||||||
|
statusBarItem.text = text;
|
||||||
|
statusBarItem.tooltip = text;
|
||||||
|
statusBarItem.show();
|
||||||
|
clearTimeout(statusBarItemTimer);
|
||||||
|
statusBarItemTimer = setTimeout(function () {
|
||||||
|
statusBarItem.hide();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this method is called when your extension is deactivated
|
||||||
|
export function deactivate(): void {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setConfiguration(name: string, value: any) {
|
||||||
|
await vscode.workspace.getConfiguration().update(name, value, true);
|
||||||
|
}
|
||||||
29
extensions/integration-tests/src/objectExplorer.test.ts
Normal file
29
extensions/integration-tests/src/objectExplorer.test.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import 'mocha';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import { context } from './testContext';
|
||||||
|
import { getDefaultTestingServer } from './testConfig';
|
||||||
|
import { connectToServer } from './utils';
|
||||||
|
import assert = require('assert');
|
||||||
|
|
||||||
|
if (context.RunTest) {
|
||||||
|
suite('Object Explorer integration test suite', () => {
|
||||||
|
test('context menu test', async function () {
|
||||||
|
await connectToServer(await getDefaultTestingServer());
|
||||||
|
let nodes = <sqlops.objectexplorer.ObjectExplorerNode[]>await sqlops.objectexplorer.getActiveConnectionNodes();
|
||||||
|
assert(nodes.length === 1, `expecting 1 active connection, actual: ${nodes.length}`);
|
||||||
|
let actions = await sqlops.objectexplorer.getNodeActions(nodes[0].connectionId, nodes[0].nodePath);
|
||||||
|
const expectedActions = ['Manage', 'New Query', 'Disconnect', 'Delete Connection', 'Refresh', 'Launch Profiler'];
|
||||||
|
|
||||||
|
const expectedString = expectedActions.join(',');
|
||||||
|
const actualString = actions.join(',');
|
||||||
|
assert(expectedActions.length === actions.length && expectedString === actualString, `Expected actions: "${expectedString}", Actual actions: "${actualString}"`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
21
extensions/integration-tests/src/setup.test.ts
Normal file
21
extensions/integration-tests/src/setup.test.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import 'mocha';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { context } from './testContext';
|
||||||
|
|
||||||
|
if (!context.RunTest) {
|
||||||
|
suite('integration test setup', () => {
|
||||||
|
test('test setup', async function () {
|
||||||
|
//Prepare the environment and make it ready for testing
|
||||||
|
await vscode.commands.executeCommand('test.setupIntegrationTest');
|
||||||
|
//Reload the window, this is required for some changes made by the 'test.setupIntegrationTest' to work
|
||||||
|
await vscode.commands.executeCommand('workbench.action.reloadWindow');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
92
extensions/integration-tests/src/testConfig.ts
Normal file
92
extensions/integration-tests/src/testConfig.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: Due to a runtime error, I duplicated this file at these 2 locations:
|
||||||
|
$/extensions/integration-test/src/testConfig.ts
|
||||||
|
$/test/smoke/src/sql/testConfig.ts
|
||||||
|
for now, make sure to keep both files in sync.
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface ITestServerProfile {
|
||||||
|
serverName: string;
|
||||||
|
userName: string;
|
||||||
|
password: string;
|
||||||
|
authenticationType: AuthenticationType;
|
||||||
|
database: string;
|
||||||
|
provider: ConnectionProvider;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface INameDisplayNamePair {
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AuthenticationType {
|
||||||
|
Windows,
|
||||||
|
SqlLogin
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ConnectionProvider {
|
||||||
|
SQLServer
|
||||||
|
}
|
||||||
|
|
||||||
|
var connectionProviderMapping = {};
|
||||||
|
var authenticationTypeMapping = {};
|
||||||
|
connectionProviderMapping[ConnectionProvider.SQLServer] = { name: 'MSSQL', displayName: 'Microsoft SQL Server' };
|
||||||
|
|
||||||
|
authenticationTypeMapping[AuthenticationType.SqlLogin] = { name: 'SqlLogin', displayName: 'SQL Login' };
|
||||||
|
authenticationTypeMapping[AuthenticationType.Windows] = { name: 'Integrated', displayName: 'Windows Authentication' };
|
||||||
|
|
||||||
|
export class TestServerProfile {
|
||||||
|
constructor(private _profile: ITestServerProfile) { }
|
||||||
|
public get serverName(): string { return this._profile.serverName; }
|
||||||
|
public get userName(): string { return this._profile.userName; }
|
||||||
|
public get password(): string { return this._profile.password; }
|
||||||
|
public get database(): string { return this._profile.database; }
|
||||||
|
public get version(): string { return this._profile.version; }
|
||||||
|
public get provider(): ConnectionProvider { return this._profile.provider; }
|
||||||
|
public get providerName(): string { return getEnumMappingEntry(connectionProviderMapping, this.provider).name; }
|
||||||
|
public get providerDisplayName(): string { return getEnumMappingEntry(connectionProviderMapping, this.provider).displayName; }
|
||||||
|
public get authenticationType(): AuthenticationType { return this._profile.authenticationType; }
|
||||||
|
public get authenticationTypeName(): string { return getEnumMappingEntry(authenticationTypeMapping, this.authenticationType).name; }
|
||||||
|
public get authenticationTypeDisplayName(): string { return getEnumMappingEntry(authenticationTypeMapping, this.authenticationType).displayName; }
|
||||||
|
}
|
||||||
|
|
||||||
|
var TestingServers: TestServerProfile[] = [
|
||||||
|
new TestServerProfile(
|
||||||
|
{
|
||||||
|
serverName: 'SQLTOOLS2017-3',
|
||||||
|
userName: '',
|
||||||
|
password: '',
|
||||||
|
authenticationType: AuthenticationType.Windows,
|
||||||
|
database: 'master',
|
||||||
|
provider: ConnectionProvider.SQLServer,
|
||||||
|
version: '2017'
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
function getEnumMappingEntry(mapping: any, enumValue: any): INameDisplayNamePair {
|
||||||
|
let entry = mapping[enumValue];
|
||||||
|
if (entry) {
|
||||||
|
return entry;
|
||||||
|
} else {
|
||||||
|
throw `Unknown enum type: ${enumValue.toString()}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDefaultTestingServer(): Promise<TestServerProfile> {
|
||||||
|
let servers = await getTestingServers();
|
||||||
|
return servers[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTestingServers(): Promise<TestServerProfile[]> {
|
||||||
|
let promise = new Promise<TestServerProfile[]>(resolve => {
|
||||||
|
resolve(TestingServers);
|
||||||
|
});
|
||||||
|
await promise;
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
8
extensions/integration-tests/src/testContext.ts
Normal file
8
extensions/integration-tests/src/testContext.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
export var context = {
|
||||||
|
RunTest: true
|
||||||
|
};
|
||||||
8
extensions/integration-tests/src/typings/ref.d.ts
vendored
Normal file
8
extensions/integration-tests/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
|
||||||
|
/// <reference path='../../../../src/sql/sqlops.proposed.d.ts'/>
|
||||||
|
/// <reference path='../../../../src/sql/sqlops.test.d.ts'/>
|
||||||
|
/// <reference types='@types/node'/>
|
||||||
38
extensions/integration-tests/src/utils.ts
Normal file
38
extensions/integration-tests/src/utils.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import assert = require('assert');
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { TestServerProfile } from './testConfig';
|
||||||
|
|
||||||
|
export async function connectToServer(server: TestServerProfile) {
|
||||||
|
let connectionProfile: sqlops.IConnectionProfile = {
|
||||||
|
serverName: server.serverName,
|
||||||
|
databaseName: server.database,
|
||||||
|
authenticationType: server.authenticationTypeName,
|
||||||
|
providerName: server.providerName,
|
||||||
|
connectionName: '',
|
||||||
|
userName: server.userName,
|
||||||
|
password: server.password,
|
||||||
|
savePassword: false,
|
||||||
|
groupFullName: undefined,
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined,
|
||||||
|
groupId: undefined,
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
await ensureConnectionViewOpened();
|
||||||
|
let result = <sqlops.ConnectionResult>await sqlops.connection.connect(connectionProfile);
|
||||||
|
assert(result.connected, `Failed to connect to "${connectionProfile.serverName}", error code: ${result.errorCode}, error message: ${result.errorMessage}`);
|
||||||
|
|
||||||
|
//workaround
|
||||||
|
//wait for OE to load
|
||||||
|
await new Promise(c => setTimeout(c, 3000));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function ensureConnectionViewOpened() {
|
||||||
|
await vscode.commands.executeCommand('workbench.view.connections');
|
||||||
|
}
|
||||||
15
extensions/integration-tests/tsconfig.json
Normal file
15
extensions/integration-tests/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "ES5",
|
||||||
|
"outDir": "out",
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"lib": [
|
||||||
|
"es2015"
|
||||||
|
],
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
2047
extensions/integration-tests/yarn.lock
Normal file
2047
extensions/integration-tests/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@
|
|||||||
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
|
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
|
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.15",
|
||||||
"opener": "^1.4.3",
|
"opener": "^1.4.3",
|
||||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||||
"vscode-extension-telemetry": "^0.0.15"
|
"vscode-extension-telemetry": "^0.0.15"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||||
"version": "1.5.0-alpha.63",
|
"version": "1.5.0-alpha.68",
|
||||||
"downloadFileNames": {
|
"downloadFileNames": {
|
||||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||||
|
|||||||
@@ -331,6 +331,14 @@ export interface DeployParams {
|
|||||||
taskExecutionMode: TaskExecutionMode;
|
taskExecutionMode: TaskExecutionMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GenerateDeployScriptParams {
|
||||||
|
packageFilePath: string;
|
||||||
|
databaseName: string;
|
||||||
|
scriptFilePath: string;
|
||||||
|
ownerUri: string;
|
||||||
|
taskExecutionMode: TaskExecutionMode;
|
||||||
|
}
|
||||||
|
|
||||||
export namespace ExportRequest {
|
export namespace ExportRequest {
|
||||||
export const type = new RequestType<ExportParams, sqlops.DacFxResult, void, void>('dacfx/export');
|
export const type = new RequestType<ExportParams, sqlops.DacFxResult, void, void>('dacfx/export');
|
||||||
}
|
}
|
||||||
@@ -347,4 +355,8 @@ export namespace DeployRequest {
|
|||||||
export const type = new RequestType<DeployParams, sqlops.DacFxResult, void, void>('dacfx/deploy');
|
export const type = new RequestType<DeployParams, sqlops.DacFxResult, void, void>('dacfx/deploy');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export namespace GenerateDeployScriptRequest {
|
||||||
|
export const type = new RequestType<GenerateDeployScriptParams, sqlops.DacFxResult, void, void>('dacfx/generateDeploymentScript');
|
||||||
|
}
|
||||||
|
|
||||||
// ------------------------------- < DacFx > ------------------------------------
|
// ------------------------------- < DacFx > ------------------------------------
|
||||||
@@ -106,12 +106,26 @@ export class DacFxServicesFeature extends SqlOpsFeature<undefined> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let generateDeployScript = (packageFilePath: string, targetDatabaseName: string, scriptFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> => {
|
||||||
|
let params: contracts.GenerateDeployScriptParams = { packageFilePath: packageFilePath, databaseName: targetDatabaseName, scriptFilePath: scriptFilePath, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||||
|
return client.sendRequest(contracts.GenerateDeployScriptRequest.type, params).then(
|
||||||
|
r => {
|
||||||
|
return r;
|
||||||
|
},
|
||||||
|
e => {
|
||||||
|
client.logFailedRequest(contracts.DeployRequest.type, e);
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return sqlops.dataprotocol.registerDacFxServicesProvider({
|
return sqlops.dataprotocol.registerDacFxServicesProvider({
|
||||||
providerId: client.providerId,
|
providerId: client.providerId,
|
||||||
exportBacpac,
|
exportBacpac,
|
||||||
importBacpac,
|
importBacpac,
|
||||||
extractDacpac,
|
extractDacpac,
|
||||||
deployDacpac
|
deployDacpac,
|
||||||
|
generateDeployScript
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"declaration": true
|
"declaration": false
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user