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}",
|
||||
"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",
|
||||
"request": "launch",
|
||||
@@ -128,6 +152,16 @@
|
||||
"args": [
|
||||
"--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": [
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,5 +1,27 @@
|
||||
# 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
|
||||
* Release date: November 6, 2018
|
||||
* 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
|
||||
-- | --
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2038320
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2038323
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2038327
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2038332
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2038401
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2038405
|
||||
Windows User Installer | https://go.microsoft.com/fwlink/?linkid=2049972
|
||||
Windows System Installer | https://go.microsoft.com/fwlink/?linkid=2049975
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2050146
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2049981
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2049986
|
||||
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.
|
||||
|
||||
@@ -62,6 +63,10 @@ The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.micro
|
||||
## 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`
|
||||
* rdaniels6813 for `Add query plan theme support #3031`
|
||||
* Ruturaj123 for `Fixed some typos and grammatical errors #3027`
|
||||
* PromoFaux for `Use emoji shortcodes in CONTRIBUTING.md instead of <20> #3009`
|
||||
|
||||
@@ -36,3 +36,7 @@ steps:
|
||||
inputs:
|
||||
testResultsFiles: '**/test-results.xml'
|
||||
condition: succeededOrFailed()
|
||||
|
||||
- script: |
|
||||
yarn run tslint
|
||||
displayName: 'Run TSLint'
|
||||
@@ -24,3 +24,7 @@ steps:
|
||||
inputs:
|
||||
testResultsFiles: 'test-results.xml'
|
||||
condition: succeededOrFailed()
|
||||
|
||||
- script: |
|
||||
yarn run tslint
|
||||
displayName: 'Run TSLint'
|
||||
@@ -65,6 +65,8 @@ const excludedExtensions = [
|
||||
'vscode-colorize-tests',
|
||||
'ms-vscode.node-debug',
|
||||
'ms-vscode.node-debug2',
|
||||
// {{SQL CARBON EDIT}}
|
||||
'integration-tests',
|
||||
];
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
|
||||
@@ -26,6 +26,7 @@ export class JobData implements IAgentDialogData {
|
||||
private _operators: string[];
|
||||
private _defaultOwner: string;
|
||||
private _jobCompletionActionConditions: sqlops.CategoryValue[];
|
||||
private _jobCategoryIdsMap: sqlops.AgentJobCategory[];
|
||||
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
public name: string;
|
||||
@@ -46,6 +47,7 @@ export class JobData implements IAgentDialogData {
|
||||
public alerts: sqlops.AgentAlertInfo[];
|
||||
public jobId: string;
|
||||
public startStepId: number;
|
||||
public categoryType: number;
|
||||
|
||||
constructor(
|
||||
ownerUri: string,
|
||||
@@ -66,6 +68,8 @@ export class JobData implements IAgentDialogData {
|
||||
this.alerts = jobInfo.alerts;
|
||||
this.jobId = jobInfo.jobId;
|
||||
this.startStepId = jobInfo.startStepId;
|
||||
this.categoryId = jobInfo.categoryId;
|
||||
this.categoryType = jobInfo.categoryType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +77,10 @@ export class JobData implements IAgentDialogData {
|
||||
return this._jobCategories;
|
||||
}
|
||||
|
||||
public get jobCategoryIdsMap(): sqlops.AgentJobCategory[] {
|
||||
return this._jobCategoryIdsMap;
|
||||
}
|
||||
|
||||
public get operators(): string[] {
|
||||
return this._operators;
|
||||
}
|
||||
@@ -96,7 +104,7 @@ export class JobData implements IAgentDialogData {
|
||||
this._jobCategories = jobDefaults.categories.map((cat) => {
|
||||
return cat.name;
|
||||
});
|
||||
|
||||
this._jobCategoryIdsMap = jobDefaults.categories;
|
||||
this._defaultOwner = jobDefaults.owner;
|
||||
|
||||
this._operators = ['', this._defaultOwner];
|
||||
@@ -164,8 +172,8 @@ export class JobData implements IAgentDialogData {
|
||||
hasSchedule: false,
|
||||
hasStep: false,
|
||||
runnable: true,
|
||||
categoryId: 0,
|
||||
categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS
|
||||
categoryId: this.categoryId,
|
||||
categoryType: this.categoryType,
|
||||
lastRun: '',
|
||||
nextRun: '',
|
||||
jobId: this.jobId,
|
||||
|
||||
@@ -94,6 +94,9 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
private editStepButton: sqlops.ButtonComponent;
|
||||
private deleteStepButton: sqlops.ButtonComponent;
|
||||
|
||||
// Schedule tab controls
|
||||
private removeScheduleButton: sqlops.ButtonComponent;
|
||||
|
||||
// Notifications tab controls
|
||||
private notificationsTabTopLabel: sqlops.TextComponent;
|
||||
private emailCheckBox: sqlops.CheckBoxComponent;
|
||||
@@ -302,6 +305,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||
this.steps[previousRow].id = previousStepId;
|
||||
this.steps[rowNumber].id = currentStepId;
|
||||
this.stepsTable.selectedRows = [previousRow];
|
||||
});
|
||||
|
||||
this.moveStepDownButton.onDidClick(() => {
|
||||
@@ -316,6 +320,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||
this.steps[nextRow].id = nextStepId;
|
||||
this.steps[rowNumber].id = currentStepId;
|
||||
this.stepsTable.selectedRows = [nextRow];
|
||||
});
|
||||
|
||||
this.editStepButton.onDidClick(() => {
|
||||
@@ -346,12 +351,12 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
if (this.stepsTable.selectedRows.length === 1) {
|
||||
let rowNumber = this.stepsTable.selectedRows[0];
|
||||
AgentUtils.getAgentService().then((agentService) => {
|
||||
let steps = this.model.jobSteps ? this.model.jobSteps : [];
|
||||
let stepData = this.model.jobSteps[rowNumber];
|
||||
let stepData = this.steps[rowNumber];
|
||||
if (stepData.jobId) {
|
||||
agentService.deleteJobStep(this.ownerUri, stepData).then((result) => {
|
||||
if (result && result.success) {
|
||||
delete steps[rowNumber];
|
||||
let data = this.convertStepsToData(steps);
|
||||
this.steps.splice(rowNumber, 1);
|
||||
let data = this.convertStepsToData(this.steps);
|
||||
this.stepsTable.data = data;
|
||||
this.startStepDropdownValues = [];
|
||||
this.steps.forEach((step) => {
|
||||
@@ -360,6 +365,16 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
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,
|
||||
width: 80
|
||||
}).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);
|
||||
pickScheduleDialog.onSuccess((dialogModel) => {
|
||||
let selectedSchedule = dialogModel.selectedSchedule;
|
||||
@@ -483,12 +502,23 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
});
|
||||
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()
|
||||
.withFormItems([{
|
||||
component: this.schedulesTable,
|
||||
title: this.SchedulesTopLabelString,
|
||||
actions: [this.pickScheduleButton]
|
||||
actions: [this.pickScheduleButton, this.removeScheduleButton]
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
@@ -499,10 +529,9 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
|
||||
private populateScheduleTable() {
|
||||
let data = this.convertSchedulesToData(this.schedules);
|
||||
if (data.length > 0) {
|
||||
this.schedulesTable.data = data;
|
||||
this.schedulesTable.height = 750;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private initializeNotificationsTab() {
|
||||
@@ -674,5 +703,6 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
this.model.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 OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
|
||||
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 FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
|
||||
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
|
||||
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
|
||||
@@ -62,6 +61,8 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
||||
|
||||
// Dropdown options
|
||||
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 NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
|
||||
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 fileBrowserNameBox: sqlops.InputBoxComponent;
|
||||
private userInputBox: sqlops.InputBoxComponent;
|
||||
private processExitCodeBox: sqlops.InputBoxComponent;
|
||||
|
||||
// Dropdowns
|
||||
private typeDropdown: sqlops.DropDownComponent;
|
||||
@@ -100,8 +102,6 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
||||
// Buttons
|
||||
private openButton: sqlops.ButtonComponent;
|
||||
private parseButton: sqlops.ButtonComponent;
|
||||
private nextButton: sqlops.ButtonComponent;
|
||||
private previousButton: sqlops.ButtonComponent;
|
||||
private outputFileBrowserButton: sqlops.ButtonComponent;
|
||||
|
||||
// Checkbox
|
||||
@@ -179,18 +179,6 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
||||
inputType: 'text'
|
||||
})
|
||||
.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) {
|
||||
@@ -208,7 +196,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
||||
this.typeDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: this.TSQLScript,
|
||||
values: [this.TSQLScript]
|
||||
values: [this.TSQLScript, this.CmdExec, this.Powershell]
|
||||
})
|
||||
.component();
|
||||
this.runAsDropdown = view.modelBuilder.dropDown()
|
||||
@@ -218,33 +206,20 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
||||
})
|
||||
.component();
|
||||
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()
|
||||
.withProperties({
|
||||
value: databases[0],
|
||||
values: databases
|
||||
}).component();
|
||||
|
||||
this.processExitCodeBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
}).component();
|
||||
this.processExitCodeBox.enabled = false;
|
||||
|
||||
// create the commands section
|
||||
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()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
@@ -258,14 +233,52 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
||||
}, {
|
||||
component: this.databaseDropdown,
|
||||
title: this.DatabaseLabelString
|
||||
}, {
|
||||
component: this.processExitCodeBox,
|
||||
title: this.ProcessExitCodeText
|
||||
}, {
|
||||
component: this.commandTextBox,
|
||||
title: this.CommandLabelString,
|
||||
actions: [buttonContainer]
|
||||
actions: [this.openButton, this.parseButton]
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: 420
|
||||
}).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();
|
||||
formWrapper.loading = false;
|
||||
await view.initializeModel(formWrapper);
|
||||
@@ -524,6 +537,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
||||
this.model.outputFileName = this.outputFileNameBox.value;
|
||||
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
|
||||
this.model.command = this.commandTextBox.value ? this.commandTextBox.value : '';
|
||||
this.model.commandExecutionSuccessCode = this.processExitCodeBox.value ? +this.processExitCodeBox.value : 0;
|
||||
}
|
||||
|
||||
public async initializeDialog() {
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
"configuration": [
|
||||
{
|
||||
"type": "object",
|
||||
"title": "%azure.config.title%",
|
||||
"title": "%azure.resource.config.title%",
|
||||
"properties": {
|
||||
"azureResource.resourceFilter": {
|
||||
"azure.resource.config.filter": {
|
||||
"type": "array",
|
||||
"default": null,
|
||||
"description": "%azure.resourceFilter.description%"
|
||||
"description": "%azure.resource.config.filter.description%"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -61,39 +61,47 @@
|
||||
"category": "Azure Accounts"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.refreshall",
|
||||
"title": "%azureresource.refreshall%",
|
||||
"command": "azure.resource.refreshall",
|
||||
"title": "%azure.resource.refreshall.title%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/refresh_inverse.svg",
|
||||
"light": "resources/light/refresh.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "azureresource.refresh",
|
||||
"title": "%azureresource.refresh%",
|
||||
"command": "azure.resource.refresh",
|
||||
"title": "%azure.resource.refresh.title%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/refresh_inverse.svg",
|
||||
"light": "resources/light/refresh.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "azureresource.signin",
|
||||
"title": "%azureresource.signin%"
|
||||
"command": "azure.resource.signin",
|
||||
"title": "%azure.resource.signin.title%"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.connectsqldb",
|
||||
"title": "%azureresource.connectsqldb%",
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"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": {
|
||||
"dark": "resources/dark/connect_to_inverse.svg",
|
||||
"light": "resources/light/connect_to.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "azureresource.selectsubscriptions",
|
||||
"title": "%azureresource.selectsubscriptions%",
|
||||
"command": "azure.resource.connectsqldb",
|
||||
"title": "%azure.resource.connectsqldb.title%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/filter_inverse.svg",
|
||||
"light": "resources/light/filter.svg"
|
||||
"dark": "resources/dark/connect_to_inverse.svg",
|
||||
"light": "resources/light/connect_to.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -110,46 +118,47 @@
|
||||
"azureResource": [
|
||||
{
|
||||
"id": "azureResourceExplorer",
|
||||
"name": "%azure.resourceExplorer.title%"
|
||||
"name": "%azure.resource.explorer.title%"
|
||||
}
|
||||
]
|
||||
},
|
||||
"menus": {
|
||||
"view/title": [
|
||||
{
|
||||
"command": "azureresource.refreshall",
|
||||
"command": "azure.resource.refreshall",
|
||||
"when": "view == azureResourceExplorer",
|
||||
"group": "navigation@1"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "azureresource.connectsqldb",
|
||||
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
|
||||
"group": "1azureresource@1"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.connectsqldb",
|
||||
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"when": "viewItem == azure.resource.itemType.account",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.selectsubscriptions",
|
||||
"when": "viewItem == azureResource.itemType.account",
|
||||
"command": "azure.resource.refresh",
|
||||
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.refresh",
|
||||
"when": "viewItem =~ /^azureResource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
|
||||
"command": "azure.resource.connectsqlserver",
|
||||
"when": "viewItem == azure.resource.itemType.databaseServer",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.connectsqldb",
|
||||
"when": "viewItem == azure.resource.itemType.database",
|
||||
"group": "inline"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"hasAzureResourceProviders": true
|
||||
},
|
||||
"dependencies": {
|
||||
"request": "2.88.0",
|
||||
"azure-arm-resource": "^7.0.0",
|
||||
"azure-arm-sql": "^5.0.1",
|
||||
"request": "2.88.0",
|
||||
"vscode-nls": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -157,6 +166,7 @@
|
||||
"@types/node": "^8.0.24",
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^13.2.1",
|
||||
"vscode": "^1.1.26",
|
||||
"typemoq": "^2.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
{
|
||||
"azure.displayName": "Azure (Core)",
|
||||
"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.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",
|
||||
|
||||
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
||||
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) 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';
|
||||
|
||||
import { window, QuickPickItem } from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { generateGuid } from './utils';
|
||||
import { ApiWrapper } from '../apiWrapper';
|
||||
import { TreeNode } from '../treeNodes';
|
||||
import { AzureResource } from 'sqlops';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import { AppContext } from '../appContext';
|
||||
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 { AzureResourceDatabaseServerTreeNode } from './tree/databaseServerTreeNode';
|
||||
import { AzureResourceDatabaseTreeNode } from './tree/databaseTreeNode';
|
||||
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
||||
import { AzureResourceServicePool } from './servicePool';
|
||||
import { AzureResourceSubscription } from './models';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../azureResource/interfaces';
|
||||
import { AzureResourceServiceNames } from './constants';
|
||||
|
||||
export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: AzureResourceTreeProvider): void {
|
||||
apiWrapper.registerCommand('azureresource.selectsubscriptions', async (node?: TreeNode) => {
|
||||
export function registerAzureResourceCommands(appContext: AppContext, tree: AzureResourceTreeProvider): void {
|
||||
appContext.apiWrapper.registerCommand('azure.resource.selectsubscriptions', async (node?: TreeNode) => {
|
||||
if (!(node instanceof AzureResourceAccountTreeNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subscriptionService = appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||
const subscriptionFilterService = appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||
|
||||
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();
|
||||
if (!subscriptions || subscriptions.length === 0) {
|
||||
const credentials = await servicePool.credentialService.getCredentials(accountNode.account, sqlops.AzureResource.ResourceManagement);
|
||||
subscriptions = await servicePool.subscriptionService.getSubscriptions(accountNode.account, credentials);
|
||||
for (const tenant of this.account.properties.tenants) {
|
||||
const token = tokens[tenant.id].token;
|
||||
const tokenType = tokens[tenant.id].tokenType;
|
||||
|
||||
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[] = [];
|
||||
if (selectedSubscriptions.length > 0) {
|
||||
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));
|
||||
}
|
||||
|
||||
interface SubscriptionQuickPickItem extends QuickPickItem {
|
||||
subscription: AzureResourceSubscription;
|
||||
interface AzureResourceSubscriptionQuickPickItem extends QuickPickItem {
|
||||
subscription: azureResource.AzureResourceSubscription;
|
||||
}
|
||||
|
||||
const subscriptionItems: SubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
||||
const subscriptionQuickPickItems: AzureResourceSubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
||||
return {
|
||||
label: subscription.name,
|
||||
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 }));
|
||||
if (pickedSubscriptionItems && pickedSubscriptionItems.length > 0) {
|
||||
const selectedSubscriptionQuickPickItems = (await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true }));
|
||||
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
|
||||
tree.refresh(node, false);
|
||||
|
||||
const pickedSubscriptions = pickedSubscriptionItems.map((subscriptionItem) => subscriptionItem.subscription);
|
||||
await servicePool.subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, pickedSubscriptions);
|
||||
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
|
||||
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);
|
||||
});
|
||||
|
||||
apiWrapper.registerCommand('azureresource.connectsqldb', async (node?: TreeNode) => {
|
||||
let connectionProfile: sqlops.IConnectionProfile = {
|
||||
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');
|
||||
appContext.apiWrapper.registerCommand('azure.resource.signin', async (node?: TreeNode) => {
|
||||
appContext.apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,11 +6,19 @@
|
||||
'use strict';
|
||||
|
||||
export enum AzureResourceItemType {
|
||||
account = 'azureResource.itemType.account',
|
||||
subscription = 'azureResource.itemType.subscription',
|
||||
databaseContainer = 'azureResource.itemType.databaseContainer',
|
||||
database = 'azureResource.itemType.database',
|
||||
databaseServerContainer = 'azureResource.itemType.databaseServerContainer',
|
||||
databaseServer = 'azureResource.itemType.databaseServer',
|
||||
message = 'azureResource.itemType.message'
|
||||
account = 'azure.resource.itemType.account',
|
||||
subscription = 'azure.resource.itemType.subscription',
|
||||
databaseContainer = 'azure.resource.itemType.databaseContainer',
|
||||
database = 'azure.resource.itemType.database',
|
||||
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
|
||||
databaseServer = 'azure.resource.itemType.databaseServer',
|
||||
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 {
|
||||
constructor(
|
||||
message: string,
|
||||
public innerError: Error
|
||||
public readonly innerError: Error
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@@ -6,49 +6,40 @@
|
||||
'use strict';
|
||||
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { Account, DidChangeAccountsParams } from 'sqlops';
|
||||
import { Event } from 'vscode';
|
||||
|
||||
import { AzureResourceSubscription, AzureResourceDatabaseServer, AzureResourceDatabase } from './models';
|
||||
import { azureResource } from './azure-resource';
|
||||
|
||||
export interface IAzureResourceAccountService {
|
||||
getAccounts(): Promise<sqlops.Account[]>;
|
||||
getAccounts(): Promise<Account[]>;
|
||||
|
||||
readonly onDidChangeAccounts: Event<sqlops.DidChangeAccountsParams>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceCredentialService {
|
||||
getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]>;
|
||||
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceSubscriptionService {
|
||||
getSubscriptions(account: sqlops.Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]>;
|
||||
getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceSubscriptionFilterService {
|
||||
getSelectedSubscriptions(account: sqlops.Account): Promise<AzureResourceSubscription[]>;
|
||||
getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
|
||||
saveSelectedSubscriptions(account: sqlops.Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceDatabaseServerService {
|
||||
getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceDatabaseService {
|
||||
getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]>;
|
||||
saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceCacheService {
|
||||
generateKey(id: string): string;
|
||||
|
||||
get<T>(key: string): T | undefined;
|
||||
|
||||
update<T>(key: string, value: T): void;
|
||||
}
|
||||
|
||||
export interface IAzureResourceContextService {
|
||||
getAbsolutePath(relativePath: string): string;
|
||||
|
||||
executeCommand(commandId: string, ...args: any[]): void;
|
||||
|
||||
showErrorMessage(errorMessage: string): void;
|
||||
export interface IAzureResourceTenantService {
|
||||
getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceNodeWithProviderId {
|
||||
resourceProviderId: string;
|
||||
resourceNode: azureResource.IAzureResourceNode;
|
||||
}
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
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 {
|
||||
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.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
@@ -6,11 +5,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
// TODO: The content of this file should be refactored to an extension
|
||||
export function getKnoxUrl(host: string, port: string): string {
|
||||
return `https://${host}:${port}/gateway`;
|
||||
}
|
||||
|
||||
export function getLivyUrl(serverName: string, port: string): string {
|
||||
return getKnoxUrl(serverName, port) + '/default/livy/v1/';
|
||||
export interface AzureResourceDatabase {
|
||||
name: string;
|
||||
serverName: string;
|
||||
serverFullName: string;
|
||||
loginName: string;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
export interface AzureResourceSubscription {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AzureResourceDatabaseServer {
|
||||
name: string;
|
||||
fullName: string;
|
||||
loginName: 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';
|
||||
|
||||
import { ExtensionContext } from "vscode";
|
||||
import { ExtensionContext } from 'vscode';
|
||||
|
||||
import { IAzureResourceCacheService } from "../interfaces";
|
||||
import { IAzureResourceCacheService } from '../interfaces';
|
||||
|
||||
export class AzureResourceCacheService implements IAzureResourceCacheService {
|
||||
public constructor(
|
||||
public readonly context: ExtensionContext
|
||||
context: ExtensionContext
|
||||
) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
public generateKey(id: string): string {
|
||||
return `${AzureResourceCacheService.cacheKeyPrefix}.${id}`;
|
||||
}
|
||||
|
||||
public get<T>(key: string): T | undefined {
|
||||
return this.context.workspaceState.get(key);
|
||||
return this._context.workspaceState.get(key);
|
||||
}
|
||||
|
||||
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 { Account } from 'sqlops';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
|
||||
interface AzureResourceSelectedSubscriptionsCache {
|
||||
selectedSubscriptions: { [accountId: string]: AzureResourceSubscription[]};
|
||||
selectedSubscriptions: { [accountId: string]: azureResource.AzureResourceSubscription[]};
|
||||
}
|
||||
|
||||
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
||||
@@ -20,12 +20,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
cacheService: IAzureResourceCacheService
|
||||
) {
|
||||
this._cacheService = cacheService;
|
||||
|
||||
this._cacheKey = this._cacheService.generateKey('selectedSubscriptions');
|
||||
}
|
||||
|
||||
public async getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]> {
|
||||
let selectedSubscriptions: AzureResourceSubscription[] = [];
|
||||
public async getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
let selectedSubscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
||||
}
|
||||
@@ -33,10 +35,10 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
return selectedSubscriptions;
|
||||
}
|
||||
|
||||
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void> {
|
||||
let selectedSubscriptionsCache: { [accountId: string]: AzureResourceSubscription[]} = {};
|
||||
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
|
||||
let selectedSubscriptionsCache: { [accountId: string]: azureResource.AzureResourceSubscription[]} = {};
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
||||
}
|
||||
@@ -47,14 +49,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
|
||||
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
||||
|
||||
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||
|
||||
const filters: string[] = [];
|
||||
for (const accountId in selectedSubscriptionsCache) {
|
||||
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;
|
||||
if (resourceFilterConfig) {
|
||||
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 _cacheService: IAzureResourceCacheService = undefined;
|
||||
private _cacheKey: string = undefined;
|
||||
|
||||
private static readonly FilterConfigName = 'resourceFilter';
|
||||
private static readonly CacheKey = 'azureResource.cache.selectedSubscriptions';
|
||||
private static readonly filterConfigName = 'azure.resource.config.filter';
|
||||
}
|
||||
|
||||
@@ -9,24 +9,19 @@ import { Account } from 'sqlops';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SubscriptionClient } from 'azure-arm-resource';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { IAzureResourceSubscriptionService } from '../interfaces';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
|
||||
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
||||
public async getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]> {
|
||||
let subscriptions: AzureResourceSubscription[] = [];
|
||||
for (let cred of credentials) {
|
||||
let subClient = new SubscriptionClient.SubscriptionClient(cred);
|
||||
try {
|
||||
let subs = await subClient.subscriptions.list();
|
||||
public async getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
const subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
const subClient = new SubscriptionClient.SubscriptionClient(credential);
|
||||
const subs = await subClient.subscriptions.list();
|
||||
subs.forEach((sub) => subscriptions.push({
|
||||
id: sub.subscriptionId,
|
||||
name: sub.displayName
|
||||
}));
|
||||
} catch (error) {
|
||||
// Swallow the exception here.
|
||||
}
|
||||
}
|
||||
|
||||
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 { NodeInfo } from 'sqlops';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureResourceItemType } from '../constants';
|
||||
|
||||
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||
@@ -19,11 +19,11 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||
}
|
||||
|
||||
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.command = {
|
||||
title: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
||||
command: 'azureresource.signin',
|
||||
title: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||
command: 'azure.resource.signin',
|
||||
arguments: [this]
|
||||
};
|
||||
return item;
|
||||
@@ -31,7 +31,7 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||
|
||||
public getNodeInfo(): NodeInfo {
|
||||
return {
|
||||
label: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
||||
label: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||
isLeaf: true,
|
||||
errorMessage: undefined,
|
||||
metadata: undefined,
|
||||
@@ -47,5 +47,5 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||
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';
|
||||
|
||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { Account, NodeInfo } from 'sqlops';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import { Account, NodeInfo, AzureResource } from 'sqlops';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import { AppContext } from '../../appContext';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureResourceCredentialError } from '../errors';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceItemType } from '../constants';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
||||
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureResourceTenantService } from '../../azureResource/interfaces';
|
||||
|
||||
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
public constructor(
|
||||
account: Account,
|
||||
public readonly account: Account,
|
||||
appContext: AppContext,
|
||||
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.setCacheKey(`${this._id}.subscriptions`);
|
||||
this._label = this.generateLabel();
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeNode[]> {
|
||||
try {
|
||||
let subscriptions: AzureResourceSubscription[] = [];
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
if (this._isClearingCache) {
|
||||
const credentials = await this.getCredentials();
|
||||
subscriptions = (await this.servicePool.subscriptionService.getSubscriptions(this.account, credentials)) || <AzureResourceSubscription[]>[];
|
||||
try {
|
||||
const tokens = await this.appContext.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
|
||||
|
||||
let cache = this.getCache<AzureResourceSubscriptionsCache>();
|
||||
if (!cache) {
|
||||
cache = { subscriptions: { } };
|
||||
for (const tenant of this.account.properties.tenants) {
|
||||
const token = tokens[tenant.id].token;
|
||||
const tokenType = tokens[tenant.id].tokenType;
|
||||
|
||||
subscriptions.push(...(await this._subscriptionService.getSubscriptions(this.account, new TokenCredentials(token, tokenType)) || <azureResource.AzureResourceSubscription[]>[]));
|
||||
}
|
||||
cache.subscriptions[this.account.key.accountId] = subscriptions;
|
||||
this.updateCache<AzureResourceSubscriptionsCache>(cache);
|
||||
} 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);
|
||||
}
|
||||
|
||||
this.updateCache<azureResource.AzureResourceSubscription[]>(subscriptions);
|
||||
|
||||
this._isClearingCache = false;
|
||||
} else {
|
||||
@@ -52,8 +67,8 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
|
||||
this._totalSubscriptionCount = subscriptions.length;
|
||||
|
||||
let selectedSubscriptions = await this.servicePool.subscriptionFilterService.getSelectedSubscriptions(this.account);
|
||||
let selectedSubscriptionIds = (selectedSubscriptions || <AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||
const selectedSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account);
|
||||
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||
if (selectedSubscriptionIds.length > 0) {
|
||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
||||
@@ -65,31 +80,33 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
this.refreshLabel();
|
||||
|
||||
if (subscriptions.length === 0) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.NoSubscriptions, this)];
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
|
||||
} 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) {
|
||||
if (error instanceof AzureResourceCredentialError) {
|
||||
this.appContext.apiWrapper.executeCommand('azure.resource.signin');
|
||||
}
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||
}
|
||||
}
|
||||
|
||||
public async getCachedSubscriptions(): Promise<AzureResourceSubscription[]> {
|
||||
const subscriptions: AzureResourceSubscription[] = [];
|
||||
const cache = this.getCache<AzureResourceSubscriptionsCache>();
|
||||
if (cache) {
|
||||
subscriptions.push(...cache.subscriptions[this.account.key.accountId]);
|
||||
}
|
||||
return subscriptions;
|
||||
public async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
return this.getCache<azureResource.AzureResourceSubscription[]>();
|
||||
}
|
||||
|
||||
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.contextValue = AzureResourceItemType.account;
|
||||
item.iconPath = {
|
||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/account_inverse.svg'),
|
||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/account.svg')
|
||||
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/account_inverse.svg'),
|
||||
light: this.appContext.extensionContext.asAbsolutePath('resources/light/account.svg')
|
||||
};
|
||||
return item;
|
||||
}
|
||||
@@ -128,10 +145,6 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
}
|
||||
}
|
||||
|
||||
protected get cacheKey(): string {
|
||||
return 'azureResource.cache.subscriptions';
|
||||
}
|
||||
|
||||
private generateLabel(): string {
|
||||
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
|
||||
|
||||
@@ -142,14 +155,14 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
return label;
|
||||
}
|
||||
|
||||
private _subscriptionService: IAzureResourceSubscriptionService = undefined;
|
||||
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService = undefined;
|
||||
private _tenantService: IAzureResourceTenantService = undefined;
|
||||
|
||||
private _id: string = undefined;
|
||||
private _label: string = undefined;
|
||||
private _totalSubscriptionCount = 0;
|
||||
private _selectedSubscriptionCount = 0;
|
||||
|
||||
private static readonly NoSubscriptions = localize('azureResource.tree.accountTreeNode.noSubscriptions', 'No Subscriptions found.');
|
||||
}
|
||||
|
||||
interface AzureResourceSubscriptionsCache {
|
||||
subscriptions: { [accountId: string]: AzureResourceSubscription[] };
|
||||
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', 'No Subscriptions found.');
|
||||
}
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import { AppContext } from '../../appContext';
|
||||
|
||||
import { AzureResourceServicePool } from '../servicePool';
|
||||
import { AzureResourceCredentialError } from '../errors';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceCacheService } from '../../azureResource/interfaces';
|
||||
import { AzureResourceServiceNames } from '../constants';
|
||||
|
||||
export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
||||
public constructor(
|
||||
public readonly appContext: AppContext,
|
||||
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||
parent: TreeNode
|
||||
) {
|
||||
@@ -22,17 +22,17 @@ export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
||||
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public readonly servicePool = AzureResourceServicePool.getInstance();
|
||||
}
|
||||
|
||||
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
|
||||
public constructor(
|
||||
public readonly account: sqlops.Account,
|
||||
appContext: AppContext,
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||
parent: TreeNode
|
||||
) {
|
||||
super(treeChangeHandler, parent);
|
||||
super(appContext, treeChangeHandler, parent);
|
||||
|
||||
this._cacheService = this.appContext.getService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService);
|
||||
}
|
||||
|
||||
public clearCache(): void {
|
||||
@@ -43,29 +43,19 @@ export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTr
|
||||
return this._isClearingCache;
|
||||
}
|
||||
|
||||
protected async getCredentials(): Promise<ServiceClientCredentials[]> {
|
||||
try {
|
||||
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 setCacheKey(id: string): void {
|
||||
this._cacheKey = this._cacheService.generateKey(id);
|
||||
}
|
||||
|
||||
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 {
|
||||
return this.servicePool.cacheService.get<T>(this.cacheKey);
|
||||
return this._cacheService.get<T>(this._cacheKey);
|
||||
}
|
||||
|
||||
protected abstract get cacheKey(): string;
|
||||
|
||||
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 { 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 { AzureResourceDatabaseContainerTreeNode } from './databaseContainerTreeNode';
|
||||
import { AzureResourceDatabaseServerContainerTreeNode } from './databaseServerContainerTreeNode';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
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 readonly subscription: AzureResourceSubscription,
|
||||
account: Account,
|
||||
public readonly account: Account,
|
||||
public readonly subscription: azureResource.AzureResourceSubscription,
|
||||
public readonly tenatId: string,
|
||||
appContext: AppContext,
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||
parent: TreeNode
|
||||
) {
|
||||
super(treeChangeHandler, parent);
|
||||
super(appContext, treeChangeHandler, parent);
|
||||
|
||||
this._children.push(new AzureResourceDatabaseContainerTreeNode(subscription, account, treeChangeHandler, this));
|
||||
this._children.push(new AzureResourceDatabaseServerContainerTreeNode(subscription, account, treeChangeHandler, this));
|
||||
this._id = `account_${this.account.key.accountId}.subscription_${this.subscription.id}.tenant_${this.tenatId}`;
|
||||
this.setCacheKey(`${this._id}.resources`);
|
||||
}
|
||||
|
||||
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> {
|
||||
let item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
|
||||
const item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
|
||||
item.contextValue = AzureResourceItemType.subscription;
|
||||
item.iconPath = {
|
||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/subscription_inverse.svg'),
|
||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/subscription.svg')
|
||||
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/subscription_inverse.svg'),
|
||||
light: this.appContext.extensionContext.asAbsolutePath('resources/light/subscription.svg')
|
||||
};
|
||||
return item;
|
||||
}
|
||||
@@ -58,8 +86,10 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase
|
||||
}
|
||||
|
||||
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';
|
||||
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import { TreeNode } from '../treeNode';
|
||||
|
||||
export interface IAzureResourceTreeChangeHandler {
|
||||
notifyNodeChanged(node: TreeNode): void;
|
||||
|
||||
@@ -6,26 +6,25 @@
|
||||
'use strict';
|
||||
|
||||
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
|
||||
import { DidChangeAccountsParams } from 'sqlops';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import { setInterval, clearInterval } from 'timers';
|
||||
import { AppContext } from '../../appContext';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AzureResourceServicePool } from '../servicePool';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureResourceAccountTreeNode } from './accountTreeNode';
|
||||
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
||||
import { AzureResourceContainerTreeNodeBase, AzureResourceTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
|
||||
export interface IAzureResourceTreeChangeHandler {
|
||||
notifyNodeChanged(node: TreeNode): void;
|
||||
}
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceAccountService } from '../../azureResource/interfaces';
|
||||
import { AzureResourceServiceNames } from '../constants';
|
||||
|
||||
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
|
||||
public constructor() {
|
||||
AzureResourceServicePool.getInstance().accountService.onDidChangeAccounts((e: DidChangeAccountsParams) => { this._onDidChangeTreeData.fire(undefined); });
|
||||
public constructor(
|
||||
public readonly appContext: AppContext
|
||||
) {
|
||||
}
|
||||
|
||||
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
||||
@@ -33,11 +32,11 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
||||
return element.getChildren(true);
|
||||
}
|
||||
|
||||
if (!this.isSystemInitialized) {
|
||||
if (!this.isSystemInitialized && !this._loadingTimer) {
|
||||
this._loadingTimer = setInterval(async () => {
|
||||
try {
|
||||
// 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
|
||||
this.isSystemInitialized = true;
|
||||
@@ -51,16 +50,16 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
||||
// System not initialized yet
|
||||
this.isSystemInitialized = false;
|
||||
}
|
||||
}, AzureResourceTreeProvider.LoadingTimerInterval);
|
||||
}, AzureResourceTreeProvider.loadingTimerInterval);
|
||||
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.Loading, undefined)];
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.loadingLabel, undefined)];
|
||||
}
|
||||
|
||||
try {
|
||||
const accounts = await AzureResourceServicePool.getInstance().accountService.getAccounts();
|
||||
const accounts = await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
|
||||
|
||||
if (accounts && accounts.length > 0) {
|
||||
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this));
|
||||
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
|
||||
} else {
|
||||
return [new AzureResourceAccountNotSignedInTreeNode()];
|
||||
}
|
||||
@@ -96,6 +95,6 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
||||
private _loadingTimer: NodeJS.Timer = undefined;
|
||||
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
|
||||
|
||||
private static readonly Loading = localize('azureResource.tree.treeProvider.loading', 'Loading ...');
|
||||
private static readonly LoadingTimerInterval = 5000;
|
||||
private static readonly loadingLabel = localize('azure.resource.tree.treeProvider.loadingLabel', 'Loading ...');
|
||||
private static readonly loadingTimerInterval = 5000;
|
||||
}
|
||||
|
||||
@@ -11,16 +11,6 @@ import * as vscode from 'vscode';
|
||||
type TreeNodePredicate = (node: TreeNode) => boolean;
|
||||
|
||||
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 {
|
||||
let path = undefined;
|
||||
if (this.parent) {
|
||||
@@ -65,13 +55,23 @@ export abstract class TreeNode {
|
||||
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
|
||||
*/
|
||||
public abstract get nodePathValue(): string;
|
||||
|
||||
abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
||||
abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
||||
|
||||
abstract getNodeInfo(): sqlops.NodeInfo;
|
||||
private _parent: TreeNode = undefined;
|
||||
}
|
||||
@@ -12,10 +12,9 @@ export function getErrorMessage(error: Error | string): string {
|
||||
return (error instanceof Error) ? error.message : error;
|
||||
}
|
||||
|
||||
|
||||
export class AzureResourceErrorMessageUtil {
|
||||
public static getErrorMessage(error: Error | string): string {
|
||||
return localize('azureResource.error', 'Error: {0}', getErrorMessage(error));
|
||||
return localize('azure.resource.error', 'Error: {0}', getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,3 +41,56 @@ export function generateGuid(): string {
|
||||
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 */
|
||||
}
|
||||
|
||||
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 constants from './constants';
|
||||
|
||||
import MainController from './controllers/mainController';
|
||||
import AzureResourceController from './controllers/azureResourceController';
|
||||
import { AppContext } from './appContext';
|
||||
import ControllerBase from './controllers/controllerBase';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
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[] = [];
|
||||
|
||||
|
||||
@@ -35,7 +40,8 @@ export function getDefaultLogLocation() {
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
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>[] = [];
|
||||
|
||||
// Create the folder for storing the token caches
|
||||
@@ -56,21 +62,19 @@ export function activate(extensionContext: vscode.ExtensionContext) {
|
||||
extensionContext.subscriptions.push(accountProviderService);
|
||||
accountProviderService.activate();
|
||||
|
||||
// Start the main controller
|
||||
let mainController = new MainController(appContext);
|
||||
controllers.push(mainController);
|
||||
extensionContext.subscriptions.push(mainController);
|
||||
activations.push(mainController.activate());
|
||||
const azureResourceController = new AzureResourceController(appContext);
|
||||
controllers.push(azureResourceController);
|
||||
extensionContext.subscriptions.push(azureResourceController);
|
||||
activations.push(azureResourceController.activate());
|
||||
|
||||
return Promise.all(activations)
|
||||
.then((results: boolean[]) => {
|
||||
for (let result of results) {
|
||||
if (!result) {
|
||||
return false;
|
||||
return {
|
||||
provideResources() {
|
||||
return [
|
||||
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), apiWrapper, extensionContext),
|
||||
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext)
|
||||
];
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
|
||||
@@ -9,8 +9,8 @@ import * as should from 'should';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
||||
import { AzureResourceItemType } from '../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../azureResource/messageTreeNode';
|
||||
|
||||
describe('AzureResourceMessageTreeNode.info', function(): 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.command).not.undefined();
|
||||
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();
|
||||
should(nodeInfo.isLeaf).true();
|
||||
|
||||
@@ -10,35 +10,38 @@ import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
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 {
|
||||
IAzureResourceCacheService,
|
||||
IAzureResourceContextService,
|
||||
IAzureResourceCredentialService,
|
||||
IAzureResourceSubscriptionService,
|
||||
IAzureResourceSubscriptionFilterService
|
||||
IAzureResourceSubscriptionFilterService,
|
||||
IAzureResourceTenantService
|
||||
} from '../../../azureResource/interfaces';
|
||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
import { generateGuid } from '../../../azureResource/utils';
|
||||
|
||||
// Mock services
|
||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
||||
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
||||
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
||||
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
|
||||
let mockTenantService: TypeMoq.IMock<IAzureResourceTenantService>;
|
||||
let mockAppContext: AppContext;
|
||||
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||
|
||||
// Mock test data
|
||||
const mockTenantId = 'mock_tenant_id';
|
||||
|
||||
const mockAccount: sqlops.Account = {
|
||||
key: {
|
||||
accountId: 'mock_account',
|
||||
@@ -49,51 +52,68 @@ const mockAccount: sqlops.Account = {
|
||||
accountType: 'Microsoft',
|
||||
contextualDisplayName: 'test'
|
||||
},
|
||||
properties: undefined,
|
||||
properties: {
|
||||
tenants: [
|
||||
{
|
||||
id: mockTenantId
|
||||
}
|
||||
]
|
||||
},
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
||||
const mockCredentials = [mockCredential];
|
||||
|
||||
const mockSubscription1: AzureResourceSubscription = {
|
||||
const mockSubscription1: azureResource.AzureResourceSubscription = {
|
||||
id: 'mock_subscription_1',
|
||||
name: 'mock subscription 1'
|
||||
};
|
||||
const mockSubscription2: AzureResourceSubscription = {
|
||||
|
||||
const mockSubscription2: azureResource.AzureResourceSubscription = {
|
||||
id: 'mock_subscription_2',
|
||||
name: 'mock subscription 2'
|
||||
};
|
||||
|
||||
const mockSubscriptions = [mockSubscription1, mockSubscription2];
|
||||
|
||||
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 {
|
||||
beforeEach(() => {
|
||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockSubscriptionCache = { subscriptions: {} };
|
||||
mockSubscriptionCache = [];
|
||||
|
||||
mockServicePool.contextService = mockContextService.object;
|
||||
mockServicePool.cacheService = mockCacheService.object;
|
||||
mockServicePool.credentialService = mockCredentialService.object;
|
||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
||||
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);
|
||||
|
||||
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.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> {
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const accountTreeNodeId = `account_${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> {
|
||||
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));
|
||||
|
||||
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();
|
||||
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> {
|
||||
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));
|
||||
|
||||
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();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
@@ -150,36 +176,41 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
||||
|
||||
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockSubscriptionCache = { subscriptions: {} };
|
||||
mockSubscriptionCache = [];
|
||||
|
||||
mockServicePool.cacheService = mockCacheService.object;
|
||||
mockServicePool.credentialService = mockCredentialService.object;
|
||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
||||
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);
|
||||
|
||||
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.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> {
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
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();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), 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());
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||
|
||||
@@ -192,43 +223,42 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
should(children).Array();
|
||||
should(children.length).equal(mockSubscriptions.length);
|
||||
|
||||
should(Object.keys(mockSubscriptionCache.subscriptions)).deepEqual([mockAccount.key.accountId]);
|
||||
should(mockSubscriptionCache.subscriptions[mockAccount.key.accountId]).deepEqual(mockSubscriptions);
|
||||
should(mockSubscriptionCache).deepEqual(mockSubscriptions);
|
||||
|
||||
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
|
||||
const child = children[ix];
|
||||
const subscription = mockSubscriptions[ix];
|
||||
|
||||
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> {
|
||||
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));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
await accountTreeNode.getChildren();
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, 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));
|
||||
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), 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.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++) {
|
||||
should(children[ix].nodePathValue).equal(`subscription_${mockSubscriptionCache.subscriptions[mockAccount.key.accountId][ix].id}`);
|
||||
for (let ix = 0; ix < mockSubscriptionCache.length; ix++) {
|
||||
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> {
|
||||
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();
|
||||
|
||||
@@ -242,10 +272,10 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): 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));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
@@ -255,23 +285,25 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
should(children.length).equal(mockFilteredSubscriptions.length);
|
||||
|
||||
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> {
|
||||
const mockError = 'Test error';
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => { throw new Error(mockError); });
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
|
||||
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();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
||||
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), 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.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.never());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(1);
|
||||
@@ -283,11 +315,32 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
|
||||
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
|
||||
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>();
|
||||
|
||||
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> {
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
accountTreeNode.clearCache();
|
||||
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 vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { AppContext } from '../../../appContext';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
||||
import { azureResource } from '../../../azureResource/azure-resource';
|
||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
|
||||
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||
import { AzureResourceService } from '../../../azureResource/resourceService';
|
||||
import { AzureResourceResourceTreeNode } from '../../../azureResource/resourceTreeNode';
|
||||
import { IAzureResourceCacheService } from '../../../azureResource/interfaces';
|
||||
import { generateGuid } from '../../../azureResource/utils';
|
||||
|
||||
// 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>;
|
||||
|
||||
@@ -42,24 +46,60 @@ const mockAccount: sqlops.Account = {
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockSubscription: AzureResourceSubscription = {
|
||||
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('AzureResourceSubscriptionTreeNode.info', function(): void {
|
||||
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>();
|
||||
|
||||
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> {
|
||||
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();
|
||||
should(treeItem.label).equal(mockSubscription.name);
|
||||
@@ -76,16 +116,52 @@ describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
||||
|
||||
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
|
||||
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>();
|
||||
|
||||
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> {
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
it('Should return resource containers.', async function(): Promise<void> {
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
|
||||
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.length).equal(2);
|
||||
should(children[0]).instanceof(AzureResourceDatabaseContainerTreeNode);
|
||||
should(children[1]).instanceof(AzureResourceDatabaseServerContainerTreeNode);
|
||||
should(children.length).equal(expectedChildren.length);
|
||||
for (const child of children) {
|
||||
should(child).instanceOf(AzureResourceResourceTreeNode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,21 +5,28 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import 'mocha';
|
||||
import { AppContext } from '../../../appContext';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
||||
import { IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
||||
import { IAzureResourceCacheService, IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
||||
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
|
||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||
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
|
||||
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>;
|
||||
|
||||
// Mock test data
|
||||
@@ -53,15 +60,23 @@ const mockAccounts = [mockAccount1, mockAccount2];
|
||||
|
||||
describe('AzureResourceTreeProvider.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
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> {
|
||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
|
||||
|
||||
const treeProvider = new AzureResourceTreeProvider();
|
||||
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||
treeProvider.isSystemInitialized = true;
|
||||
|
||||
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> {
|
||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
|
||||
|
||||
const treeProvider = new AzureResourceTreeProvider();
|
||||
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||
treeProvider.isSystemInitialized = true;
|
||||
|
||||
const children = await treeProvider.getChildren(undefined);
|
||||
@@ -97,7 +112,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
|
||||
const mockAccountError = 'Test account error';
|
||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
|
||||
|
||||
const treeProvider = new AzureResourceTreeProvider();
|
||||
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||
treeProvider.isSystemInitialized = true;
|
||||
|
||||
const children = await treeProvider.getChildren(undefined);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true
|
||||
"declaration": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -67,7 +67,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.15",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||
"vscode-extension-telemetry": "0.0.18",
|
||||
|
||||
@@ -45,4 +45,6 @@ export interface DacFxDataModel extends BaseDataModel {
|
||||
filePath: string;
|
||||
version: string;
|
||||
upgradeExisting: boolean;
|
||||
scriptFilePath: string;
|
||||
generateScriptAndDeploy: boolean;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as nls from 'vscode-nls';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { SelectOperationPage } from './pages/selectOperationpage';
|
||||
import { DeployConfigPage } from './pages/deployConfigPage';
|
||||
import { DeployActionPage } from './pages/deployActionPage';
|
||||
import { DacFxSummaryPage } from './pages/dacFxSummaryPage';
|
||||
import { ExportConfigPage } from './pages/exportConfigPage';
|
||||
import { ExtractConfigPage } from './pages/extractConfigPage';
|
||||
@@ -30,7 +31,33 @@ export enum Operation {
|
||||
deploy,
|
||||
extract,
|
||||
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 {
|
||||
@@ -60,6 +87,7 @@ export class DataTierApplicationWizard {
|
||||
this.wizard = sqlops.window.modelviewdialog.createWizard('Data-tier Application Wizard');
|
||||
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 deployActionWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.deployActionPageName', 'Select Action'));
|
||||
let summaryWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.summaryPageName', 'Summary'));
|
||||
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'));
|
||||
@@ -67,6 +95,7 @@ export class DataTierApplicationWizard {
|
||||
|
||||
this.pages.set('selectOperation', new Page(selectOperationWizardPage));
|
||||
this.pages.set('deployConfig', new Page(deployConfigWizardPage));
|
||||
this.pages.set('deployAction', new Page(deployActionWizardPage));
|
||||
this.pages.set('extractConfig', new Page(extractConfigWizardPage));
|
||||
this.pages.set('importConfig', new Page(importConfigWizardPage));
|
||||
this.pages.set('exportConfig', new Page(exportConfigWizardPage));
|
||||
@@ -87,6 +116,12 @@ export class DataTierApplicationWizard {
|
||||
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) => {
|
||||
let extractConfigDacFxPage = new ExtractConfigPage(this, extractConfigWizardPage, this.model, view);
|
||||
this.pages.get('extractConfig').dacFxPage = extractConfigDacFxPage;
|
||||
@@ -113,39 +148,27 @@ export class DataTierApplicationWizard {
|
||||
|
||||
this.wizard.onPageChanged(async (event) => {
|
||||
let idx = event.newPage;
|
||||
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 (idx === 2) {
|
||||
page = this.pages.get('summary');
|
||||
}
|
||||
let page = this.getPage(idx);
|
||||
|
||||
if (page !== undefined) {
|
||||
page.dacFxPage.setupNavigationValidator();
|
||||
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.onClick(async () => await this.generateDeployScript());
|
||||
this.wizard.doneButton.onClick(async () => await this.executeOperation());
|
||||
|
||||
this.wizard.open();
|
||||
@@ -177,6 +200,15 @@ export class DataTierApplicationWizard {
|
||||
this.selectedOperation = Operation.export;
|
||||
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();
|
||||
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> {
|
||||
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(providerName, sqlops.DataProviderType.DacFxServicesProvider);
|
||||
return service;
|
||||
|
||||
@@ -49,6 +49,14 @@ export class DacFxSummaryPage extends BasePage {
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
this.populateTable();
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -68,6 +76,10 @@ export class DacFxSummaryPage extends BasePage {
|
||||
let sourceServer = localize('dacfx.sourceServerName', 'Source Server');
|
||||
let sourceDatabase = localize('dacfx.sourceDatabaseName', 'Source Database');
|
||||
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) {
|
||||
case Operation.deploy: {
|
||||
@@ -75,6 +87,13 @@ export class DacFxSummaryPage extends BasePage {
|
||||
[targetServer, this.model.serverName],
|
||||
[fileLocation, this.model.filePath],
|
||||
[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;
|
||||
}
|
||||
case Operation.extract: {
|
||||
@@ -99,12 +118,21 @@ export class DacFxSummaryPage extends BasePage {
|
||||
[fileLocation, this.model.filePath]];
|
||||
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({
|
||||
data: data,
|
||||
columns: ['Setting', 'Value'],
|
||||
width: 600,
|
||||
width: 700,
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,7 @@ export class DeployConfigPage extends DacFxConfigPage {
|
||||
this.model.database = this.databaseTextBox.value;
|
||||
});
|
||||
|
||||
// Initialize with upgrade existing true
|
||||
//Initialize with upgrade existing true
|
||||
upgradeRadioButton.checked = true;
|
||||
this.model.upgradeExisting = true;
|
||||
|
||||
@@ -152,7 +152,7 @@ export class DeployConfigPage extends DacFxConfigPage {
|
||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
// Handle database changes
|
||||
//Handle database changes
|
||||
this.databaseDropdown.onValueChanged(async () => {
|
||||
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
});
|
||||
@@ -172,7 +172,8 @@ export class DeployConfigPage extends DacFxConfigPage {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,11 +58,6 @@ export class SelectOperationPage extends BasePage {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -74,12 +69,14 @@ export class SelectOperationPage extends BasePage {
|
||||
}).component();
|
||||
|
||||
this.deployRadioButton.onDidClick(() => {
|
||||
// remove the previous page
|
||||
this.instance.wizard.removePage(1);
|
||||
this.removePages();
|
||||
|
||||
// add deploy page
|
||||
let page = this.instance.pages.get('deployConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
//add deploy pages
|
||||
let configPage = this.instance.pages.get('deployConfig');
|
||||
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
|
||||
this.instance.setDoneButton(Operation.deploy);
|
||||
@@ -99,12 +96,12 @@ export class SelectOperationPage extends BasePage {
|
||||
}).component();
|
||||
|
||||
this.extractRadioButton.onDidClick(() => {
|
||||
// remove the previous pages
|
||||
this.instance.wizard.removePage(1);
|
||||
this.removePages();
|
||||
|
||||
// add the extract page
|
||||
let page = this.instance.pages.get('extractConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
this.addSummaryPage(2);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.extract);
|
||||
@@ -124,12 +121,12 @@ export class SelectOperationPage extends BasePage {
|
||||
}).component();
|
||||
|
||||
this.importRadioButton.onDidClick(() => {
|
||||
// remove the previous page
|
||||
this.instance.wizard.removePage(1);
|
||||
this.removePages();
|
||||
|
||||
// add the import page
|
||||
let page = this.instance.pages.get('importConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
this.addSummaryPage(2);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.import);
|
||||
@@ -149,12 +146,12 @@ export class SelectOperationPage extends BasePage {
|
||||
}).component();
|
||||
|
||||
this.exportRadioButton.onDidClick(() => {
|
||||
// remove the 2 previous pages
|
||||
this.instance.wizard.removePage(1);
|
||||
this.removePages();
|
||||
|
||||
// add the export pages
|
||||
let page = this.instance.pages.get('exportConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
this.addSummaryPage(2);
|
||||
|
||||
// change button text and operation
|
||||
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() {
|
||||
this.instance.registerNavigationValidator(() => {
|
||||
return true;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true
|
||||
"declaration": false
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
||||
@@ -5,14 +5,12 @@
|
||||
agent-base@4, agent-base@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
|
||||
dependencies:
|
||||
es6-promisify "^5.0.0"
|
||||
|
||||
applicationinsights@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
||||
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
|
||||
dependencies:
|
||||
diagnostic-channel "0.2.0"
|
||||
diagnostic-channel-publishers "0.2.1"
|
||||
@@ -21,12 +19,10 @@ applicationinsights@1.0.1:
|
||||
base64-js@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||
integrity sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=
|
||||
|
||||
bl@^1.0.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
|
||||
dependencies:
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
@@ -34,12 +30,10 @@ bl@^1.0.0:
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
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:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
||||
dependencies:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
@@ -47,17 +41,14 @@ buffer-alloc@^1.2.0:
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
||||
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
|
||||
|
||||
buffer@^3.0.1:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
||||
integrity sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=
|
||||
dependencies:
|
||||
base64-js "0.0.8"
|
||||
ieee754 "^1.1.4"
|
||||
@@ -66,39 +57,34 @@ buffer@^3.0.1:
|
||||
commander@~2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||
integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
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":
|
||||
version "0.2.10"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.15":
|
||||
version "0.2.15"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a2cd2db109de882f0959f7b6421c86afa585f460"
|
||||
dependencies:
|
||||
vscode-languageclient "3.5.1"
|
||||
|
||||
debug@3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.1.0:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||
integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
|
||||
dependencies:
|
||||
file-type "^5.2.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:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
||||
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
|
||||
dependencies:
|
||||
decompress-tar "^4.1.0"
|
||||
file-type "^6.1.0"
|
||||
@@ -118,7 +103,6 @@ decompress-tarbz2@^4.0.0:
|
||||
decompress-targz@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
||||
integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
|
||||
dependencies:
|
||||
decompress-tar "^4.1.1"
|
||||
file-type "^5.2.0"
|
||||
@@ -127,7 +111,6 @@ decompress-targz@^4.0.0:
|
||||
decompress-unzip@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
||||
integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k=
|
||||
dependencies:
|
||||
file-type "^3.8.0"
|
||||
get-stream "^2.2.0"
|
||||
@@ -137,7 +120,6 @@ decompress-unzip@^4.0.1:
|
||||
decompress@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
||||
integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=
|
||||
dependencies:
|
||||
decompress-tar "^4.0.0"
|
||||
decompress-tarbz2 "^4.0.0"
|
||||
@@ -151,70 +133,58 @@ decompress@^4.2.0:
|
||||
diagnostic-channel-publishers@0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
||||
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
|
||||
|
||||
diagnostic-channel@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
||||
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
|
||||
dependencies:
|
||||
semver "^5.3.0"
|
||||
|
||||
end-of-stream@^1.0.0:
|
||||
version "1.4.1"
|
||||
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:
|
||||
once "^1.4.0"
|
||||
|
||||
es6-promise@^4.0.3:
|
||||
version "4.2.5"
|
||||
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:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
||||
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
|
||||
dependencies:
|
||||
es6-promise "^4.0.3"
|
||||
|
||||
eventemitter2@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
||||
integrity sha1-YZegldX7a1folC9v1+qtY6CclFI=
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
file-type@^3.8.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||
integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
|
||||
|
||||
file-type@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
||||
integrity sha1-LdvqfHP/42No365J3DOMBYwritY=
|
||||
|
||||
file-type@^6.1.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||
integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
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:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||
integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=
|
||||
dependencies:
|
||||
object-assign "^4.0.1"
|
||||
pinkie-promise "^2.0.0"
|
||||
@@ -222,17 +192,14 @@ get-stream@^2.2.0:
|
||||
graceful-fs@^4.1.10:
|
||||
version "4.1.15"
|
||||
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":
|
||||
version "1.0.1"
|
||||
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:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
|
||||
dependencies:
|
||||
agent-base "4"
|
||||
debug "3.1.0"
|
||||
@@ -240,7 +207,6 @@ http-proxy-agent@^2.1.0:
|
||||
https-proxy-agent@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
|
||||
dependencies:
|
||||
agent-base "^4.1.0"
|
||||
debug "^3.1.0"
|
||||
@@ -248,115 +214,94 @@ https-proxy-agent@^2.2.1:
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||
integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==
|
||||
|
||||
inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
is-natural-number@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
||||
integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
|
||||
|
||||
is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
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:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
make-dir@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
|
||||
dependencies:
|
||||
pify "^3.0.0"
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.1"
|
||||
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:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||
|
||||
once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
opener@^1.4.3:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
||||
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
|
||||
|
||||
os-tmpdir@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
||||
|
||||
pify@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
|
||||
|
||||
pify@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
|
||||
|
||||
pinkie-promise@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||
integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
|
||||
dependencies:
|
||||
pinkie "^2.0.0"
|
||||
|
||||
pinkie@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
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:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
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:
|
||||
version "5.1.2"
|
||||
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:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
||||
integrity sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=
|
||||
dependencies:
|
||||
commander "~2.8.1"
|
||||
|
||||
semver@^5.3.0:
|
||||
version "5.6.0"
|
||||
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":
|
||||
version "0.1.5"
|
||||
@@ -397,21 +339,18 @@ semver@^5.3.0:
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-dirs@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
||||
integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
|
||||
dependencies:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
tar-stream@^1.5.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.2.0"
|
||||
@@ -424,24 +363,20 @@ tar-stream@^1.5.2:
|
||||
through@^2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
to-buffer@^1.1.1:
|
||||
version "1.1.1"
|
||||
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:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
||||
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
|
||||
dependencies:
|
||||
buffer "^3.0.1"
|
||||
through "^2.3.6"
|
||||
@@ -449,31 +384,26 @@ unbzip2-stream@^1.0.9:
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
vscode-extension-telemetry@0.0.18:
|
||||
version "0.0.18"
|
||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
|
||||
integrity sha512-Vw3Sr+dZwl+c6PlsUwrTtCOJkgrmvS3OUVDQGcmpXWAgq9xGq6as0K4pUx+aGqTjzLAESmWSrs6HlJm6J6Khcg==
|
||||
dependencies:
|
||||
applicationinsights "1.0.1"
|
||||
|
||||
vscode-jsonrpc@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
||||
integrity sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=
|
||||
|
||||
vscode-languageclient@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
||||
integrity sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "3.5.1"
|
||||
|
||||
vscode-languageserver-protocol@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
||||
integrity sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==
|
||||
dependencies:
|
||||
vscode-jsonrpc "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:
|
||||
version "3.5.0"
|
||||
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:
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
|
||||
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
xtend@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
||||
|
||||
yauzl@^2.4.2:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
@@ -509,4 +434,3 @@ yauzl@^2.4.2:
|
||||
zone.js@0.7.6:
|
||||
version "0.7.6"
|
||||
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"
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.15",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||
"vscode-extension-telemetry": "^0.0.15"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"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": {
|
||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||
|
||||
@@ -331,6 +331,14 @@ export interface DeployParams {
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface GenerateDeployScriptParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
scriptFilePath: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export namespace ExportRequest {
|
||||
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 namespace GenerateDeployScriptRequest {
|
||||
export const type = new RequestType<GenerateDeployScriptParams, sqlops.DacFxResult, void, void>('dacfx/generateDeploymentScript');
|
||||
}
|
||||
|
||||
// ------------------------------- < 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({
|
||||
providerId: client.providerId,
|
||||
exportBacpac,
|
||||
importBacpac,
|
||||
extractDacpac,
|
||||
deployDacpac
|
||||
deployDacpac,
|
||||
generateDeployScript
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true
|
||||
"declaration": false
|
||||
},
|
||||
"exclude": [
|
||||
"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