mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-22 11:01:37 -05:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a30878599 | ||
|
|
c8a8935db0 | ||
|
|
ec196f57bb | ||
|
|
f7809ec3a7 | ||
|
|
71d3ec3616 | ||
|
|
4a7cf8d870 | ||
|
|
4bf8836c0a | ||
|
|
1ca36ee29c | ||
|
|
3446ff88cf | ||
|
|
de5a91a13f | ||
|
|
814cd73019 | ||
|
|
c21611661b | ||
|
|
8f817ce689 | ||
|
|
971b5111e7 | ||
|
|
07069a64ae | ||
|
|
6acea51f12 | ||
|
|
7aa2dab307 | ||
|
|
3091be8f67 | ||
|
|
487531cc52 | ||
|
|
58bfcb4273 | ||
|
|
8d8be27f22 | ||
|
|
27a978cba5 | ||
|
|
71b4e6afa4 | ||
|
|
e1f3b19c0c | ||
|
|
649c2aa5a6 | ||
|
|
cac8cc99e1 | ||
|
|
cb162b16f2 | ||
|
|
86e54ce145 |
@@ -2,7 +2,7 @@
|
|||||||
"name": "agent",
|
"name": "agent",
|
||||||
"displayName": "SQL Server Agent",
|
"displayName": "SQL Server Agent",
|
||||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||||
"version": "0.35.1",
|
"version": "0.35.2",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export class JobData implements IAgentDialogData {
|
|||||||
public jobSchedules: sqlops.AgentJobScheduleInfo[];
|
public jobSchedules: sqlops.AgentJobScheduleInfo[];
|
||||||
public alerts: sqlops.AgentAlertInfo[];
|
public alerts: sqlops.AgentAlertInfo[];
|
||||||
public jobId: string;
|
public jobId: string;
|
||||||
|
public startStepId: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
ownerUri: string,
|
ownerUri: string,
|
||||||
@@ -60,10 +61,11 @@ export class JobData implements IAgentDialogData {
|
|||||||
this.category = jobInfo.category;
|
this.category = jobInfo.category;
|
||||||
this.description = jobInfo.description;
|
this.description = jobInfo.description;
|
||||||
this.enabled = jobInfo.enabled;
|
this.enabled = jobInfo.enabled;
|
||||||
this.jobSteps = jobInfo.JobSteps;
|
this.jobSteps = jobInfo.jobSteps;
|
||||||
this.jobSchedules = jobInfo.JobSchedules;
|
this.jobSchedules = jobInfo.jobSchedules;
|
||||||
this.alerts = jobInfo.Alerts;
|
this.alerts = jobInfo.alerts;
|
||||||
this.jobId = jobInfo.jobId;
|
this.jobId = jobInfo.jobId;
|
||||||
|
this.startStepId = jobInfo.startStepId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,17 +143,17 @@ export class JobData implements IAgentDialogData {
|
|||||||
name: this.name,
|
name: this.name,
|
||||||
owner: this.owner,
|
owner: this.owner,
|
||||||
description: this.description,
|
description: this.description,
|
||||||
EmailLevel: this.emailLevel,
|
emailLevel: this.emailLevel,
|
||||||
PageLevel: this.pageLevel,
|
pageLevel: this.pageLevel,
|
||||||
EventLogLevel: this.eventLogLevel,
|
eventLogLevel: this.eventLogLevel,
|
||||||
DeleteLevel: this.deleteLevel,
|
deleteLevel: this.deleteLevel,
|
||||||
OperatorToEmail: this.operatorToEmail,
|
operatorToEmail: this.operatorToEmail,
|
||||||
OperatorToPage: this.operatorToPage,
|
operatorToPage: this.operatorToPage,
|
||||||
enabled: this.enabled,
|
enabled: this.enabled,
|
||||||
category: this.category,
|
category: this.category,
|
||||||
Alerts: this.alerts,
|
alerts: this.alerts,
|
||||||
JobSchedules: this.jobSchedules,
|
jobSchedules: this.jobSchedules,
|
||||||
JobSteps: this.jobSteps,
|
jobSteps: this.jobSteps,
|
||||||
// The properties below are not collected from UI
|
// The properties below are not collected from UI
|
||||||
// We could consider using a seperate class for create job request
|
// We could consider using a seperate class for create job request
|
||||||
//
|
//
|
||||||
@@ -166,7 +168,8 @@ export class JobData implements IAgentDialogData {
|
|||||||
categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS
|
categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS
|
||||||
lastRun: '',
|
lastRun: '',
|
||||||
nextRun: '',
|
nextRun: '',
|
||||||
jobId: this.jobId
|
jobId: this.jobId,
|
||||||
|
startStepId: this.startStepId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,9 @@ export abstract class AgentDialog<T extends IAgentDialogData> {
|
|||||||
public readonly onSuccess: vscode.Event<T> = this._onSuccess.event;
|
public readonly onSuccess: vscode.Event<T> = this._onSuccess.event;
|
||||||
public dialog: sqlops.window.modelviewdialog.Dialog;
|
public dialog: sqlops.window.modelviewdialog.Dialog;
|
||||||
|
|
||||||
|
// Dialog Name for Telemetry
|
||||||
|
public dialogName: string;
|
||||||
|
|
||||||
constructor(public ownerUri: string, public model: T, public title: string) {
|
constructor(public ownerUri: string, public model: T, public title: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,8 +34,9 @@ export abstract class AgentDialog<T extends IAgentDialogData> {
|
|||||||
|
|
||||||
protected abstract async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog);
|
protected abstract async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog);
|
||||||
|
|
||||||
public async openDialog() {
|
public async openDialog(dialogName?: string) {
|
||||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.title);
|
let event = dialogName ? dialogName : null;
|
||||||
|
this.dialog = sqlops.window.modelviewdialog.createDialog(this.title, event);
|
||||||
|
|
||||||
await this.model.initialize();
|
await this.model.initialize();
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,10 @@ export class AlertDialog extends AgentDialog<AlertData> {
|
|||||||
private static readonly DelayMinutesTextBoxLabel: string = localize('alertDialog.DelayMinutes', 'Delay Minutes');
|
private static readonly DelayMinutesTextBoxLabel: string = localize('alertDialog.DelayMinutes', 'Delay Minutes');
|
||||||
private static readonly DelaySecondsTextBoxLabel: string = localize('alertDialog.DelaySeconds', 'Delay Seconds');
|
private static readonly DelaySecondsTextBoxLabel: string = localize('alertDialog.DelaySeconds', 'Delay Seconds');
|
||||||
|
|
||||||
|
// Event Name strings
|
||||||
|
private readonly NewAlertDialog = 'NewAlertDialogOpen';
|
||||||
|
private readonly EditAlertDialog = 'EditAlertDialogOpened';
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
private responseTab: sqlops.window.modelviewdialog.DialogTab;
|
private responseTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
@@ -149,6 +153,7 @@ export class AlertDialog extends AgentDialog<AlertData> {
|
|||||||
private delayMinutesTextBox: sqlops.InputBoxComponent;
|
private delayMinutesTextBox: sqlops.InputBoxComponent;
|
||||||
private delaySecondsTextBox: sqlops.InputBoxComponent;
|
private delaySecondsTextBox: sqlops.InputBoxComponent;
|
||||||
|
|
||||||
|
private isEdit: boolean = false;
|
||||||
private databases: string[];
|
private databases: string[];
|
||||||
private jobModel: JobData;
|
private jobModel: JobData;
|
||||||
public jobId: string;
|
public jobId: string;
|
||||||
@@ -166,6 +171,8 @@ export class AlertDialog extends AgentDialog<AlertData> {
|
|||||||
this.jobModel = jobModel;
|
this.jobModel = jobModel;
|
||||||
this.jobId = this.jobId ? this.jobId : this.jobModel.jobId;
|
this.jobId = this.jobId ? this.jobId : this.jobModel.jobId;
|
||||||
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
|
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
|
||||||
|
this.isEdit = alertInfo ? true : false;
|
||||||
|
this.dialogName = this.isEdit ? this.EditAlertDialog : this.NewAlertDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||||
|
|||||||
@@ -42,11 +42,12 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
|
private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
|
||||||
private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
|
private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
|
||||||
private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
|
private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
|
||||||
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New...');
|
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New Step');
|
||||||
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit');
|
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit Step');
|
||||||
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete');
|
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete Step');
|
||||||
private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
|
private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
|
||||||
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Down');
|
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Down');
|
||||||
|
private readonly StartStepDropdownString: string = localize('jobDialog.startStepAt', 'Start step');
|
||||||
|
|
||||||
// Notifications tab strings
|
// Notifications tab strings
|
||||||
private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
|
private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
|
||||||
@@ -67,6 +68,10 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private readonly AlertEnabledLabelString: string = localize('jobDialog.alertEnabledLabel', 'Enabled');
|
private readonly AlertEnabledLabelString: string = localize('jobDialog.alertEnabledLabel', 'Enabled');
|
||||||
private readonly AlertTypeLabelString: string = localize('jobDialog.alertTypeLabel', 'Type');
|
private readonly AlertTypeLabelString: string = localize('jobDialog.alertTypeLabel', 'Type');
|
||||||
|
|
||||||
|
// Event Name strings
|
||||||
|
private readonly NewJobDialogEvent: string = 'NewJobDialogOpened';
|
||||||
|
private readonly EditJobDialogEvent: string = 'EditJobDialogOpened';
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
private stepsTab: sqlops.window.modelviewdialog.DialogTab;
|
private stepsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
@@ -101,6 +106,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private eventLogConditionDropdown: sqlops.DropDownComponent;
|
private eventLogConditionDropdown: sqlops.DropDownComponent;
|
||||||
private deleteJobCheckBox: sqlops.CheckBoxComponent;
|
private deleteJobCheckBox: sqlops.CheckBoxComponent;
|
||||||
private deleteJobConditionDropdown: sqlops.DropDownComponent;
|
private deleteJobConditionDropdown: sqlops.DropDownComponent;
|
||||||
|
private startStepDropdown: sqlops.DropDownComponent;
|
||||||
|
|
||||||
// Schedule tab controls
|
// Schedule tab controls
|
||||||
private schedulesTable: sqlops.TableComponent;
|
private schedulesTable: sqlops.TableComponent;
|
||||||
@@ -115,6 +121,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
private steps: sqlops.AgentJobStepInfo[];
|
private steps: sqlops.AgentJobStepInfo[];
|
||||||
private schedules: sqlops.AgentJobScheduleInfo[];
|
private schedules: sqlops.AgentJobScheduleInfo[];
|
||||||
private alerts: sqlops.AgentAlertInfo[] = [];
|
private alerts: sqlops.AgentAlertInfo[] = [];
|
||||||
|
private startStepDropdownValues: sqlops.CategoryValue[] = [];
|
||||||
|
|
||||||
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
|
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
|
||||||
super(
|
super(
|
||||||
@@ -125,6 +132,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.schedules = this.model.jobSchedules ? this.model.jobSchedules : [];
|
this.schedules = this.model.jobSchedules ? this.model.jobSchedules : [];
|
||||||
this.alerts = this.model.alerts ? this.model.alerts : [];
|
this.alerts = this.model.alerts ? this.model.alerts : [];
|
||||||
this.isEdit = jobInfo ? true : false;
|
this.isEdit = jobInfo ? true : false;
|
||||||
|
this.dialogName = this.isEdit ? this.EditJobDialogEvent : this.NewJobDialogEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initializeDialog() {
|
protected async initializeDialog() {
|
||||||
@@ -218,13 +226,20 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.StepsTable_FailureColumnString
|
this.StepsTable_FailureColumnString
|
||||||
],
|
],
|
||||||
data: data,
|
data: data,
|
||||||
height: 750
|
height: 650
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
|
this.startStepDropdown = view.modelBuilder.dropDown().withProperties({ width: 180 }).component();
|
||||||
|
this.startStepDropdown.enabled = this.steps.length > 1 ? true : false;
|
||||||
|
this.steps.forEach((step) => {
|
||||||
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
|
});
|
||||||
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
|
|
||||||
this.moveStepUpButton = view.modelBuilder.button()
|
this.moveStepUpButton = view.modelBuilder.button()
|
||||||
.withProperties({
|
.withProperties({
|
||||||
label: this.MoveStepUpButtonString,
|
label: this.MoveStepUpButtonString,
|
||||||
width: 80
|
width: 120
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.moveStepDownButton = view.modelBuilder.button()
|
this.moveStepDownButton = view.modelBuilder.button()
|
||||||
@@ -238,7 +253,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
|
|
||||||
this.newStepButton = view.modelBuilder.button().withProperties({
|
this.newStepButton = view.modelBuilder.button().withProperties({
|
||||||
label: this.NewStepButtonString,
|
label: this.NewStepButtonString,
|
||||||
width: 80
|
width: 140
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, null, true);
|
let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, null, true);
|
||||||
@@ -246,6 +261,11 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
let stepInfo = JobStepData.convertToAgentJobStepInfo(step);
|
let stepInfo = JobStepData.convertToAgentJobStepInfo(step);
|
||||||
this.steps.push(stepInfo);
|
this.steps.push(stepInfo);
|
||||||
this.stepsTable.data = this.convertStepsToData(this.steps);
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
|
this.startStepDropdownValues = [];
|
||||||
|
this.steps.forEach((step) => {
|
||||||
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
|
});
|
||||||
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
});
|
});
|
||||||
this.newStepButton.onDidClick((e)=>{
|
this.newStepButton.onDidClick((e)=>{
|
||||||
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
|
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
|
||||||
@@ -258,12 +278,12 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
|
|
||||||
this.editStepButton = view.modelBuilder.button().withProperties({
|
this.editStepButton = view.modelBuilder.button().withProperties({
|
||||||
label: this.EditStepButtonString,
|
label: this.EditStepButtonString,
|
||||||
width: 80
|
width: 140
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.deleteStepButton = view.modelBuilder.button().withProperties({
|
this.deleteStepButton = view.modelBuilder.button().withProperties({
|
||||||
label: this.DeleteStepButtonString,
|
label: this.DeleteStepButtonString,
|
||||||
width: 80
|
width: 140
|
||||||
}).component();
|
}).component();
|
||||||
|
|
||||||
this.stepsTable.enabled = false;
|
this.stepsTable.enabled = false;
|
||||||
@@ -271,41 +291,31 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.deleteStepButton.enabled = false;
|
this.deleteStepButton.enabled = false;
|
||||||
|
|
||||||
this.moveStepUpButton.onDidClick(() => {
|
this.moveStepUpButton.onDidClick(() => {
|
||||||
if (this.stepsTable.selectedRows.length === 1) {
|
let rowNumber = this.stepsTable.selectedRows[0];
|
||||||
let rowNumber = this.stepsTable.selectedRows[0];
|
let previousRow = rowNumber - 1;
|
||||||
// if it's not the first step
|
let previousStep = this.steps[previousRow];
|
||||||
if (rowNumber !== 0) {
|
let previousStepId = this.steps[previousRow].id;
|
||||||
let previousRow = rowNumber - 1;
|
let currentStep = this.steps[rowNumber];
|
||||||
let previousStep = this.steps[previousRow];
|
let currentStepId = this.steps[rowNumber].id;
|
||||||
let previousStepId = this.steps[previousRow].id;
|
this.steps[previousRow] = currentStep;
|
||||||
let currentStep = this.steps[rowNumber];
|
this.steps[rowNumber] = previousStep;
|
||||||
let currentStepId = this.steps[rowNumber].id;
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
this.steps[previousRow] = currentStep;
|
this.steps[previousRow].id = previousStepId;
|
||||||
this.steps[rowNumber] = previousStep;
|
this.steps[rowNumber].id = currentStepId;
|
||||||
this.stepsTable.data = this.convertStepsToData(this.steps);
|
|
||||||
this.steps[previousRow].id = previousStepId;
|
|
||||||
this.steps[rowNumber].id = currentStepId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.moveStepDownButton.onDidClick(() => {
|
this.moveStepDownButton.onDidClick(() => {
|
||||||
if (this.stepsTable.selectedRows.length === 1) {
|
let rowNumber = this.stepsTable.selectedRows[0];
|
||||||
let rowNumber = this.stepsTable.selectedRows[0];
|
let nextRow = rowNumber + 1;
|
||||||
// if it's not the last step
|
let nextStep = this.steps[nextRow];
|
||||||
if (this.steps.length !== rowNumber + 1) {
|
let nextStepId = this.steps[nextRow].id;
|
||||||
let nextRow = rowNumber + 1;
|
let currentStep = this.steps[rowNumber];
|
||||||
let nextStep = this.steps[nextRow];
|
let currentStepId = this.steps[rowNumber].id;
|
||||||
let nextStepId = this.steps[nextRow].id;
|
this.steps[nextRow] = currentStep;
|
||||||
let currentStep = this.steps[rowNumber];
|
this.steps[rowNumber] = nextStep;
|
||||||
let currentStepId = this.steps[rowNumber].id;
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
this.steps[nextRow] = currentStep;
|
this.steps[nextRow].id = nextStepId;
|
||||||
this.steps[rowNumber] = nextStep;
|
this.steps[rowNumber].id = currentStepId;
|
||||||
this.stepsTable.data = this.convertStepsToData(this.steps);
|
|
||||||
this.steps[nextRow].id = nextStepId;
|
|
||||||
this.steps[rowNumber].id = currentStepId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.editStepButton.onDidClick(() => {
|
this.editStepButton.onDidClick(() => {
|
||||||
@@ -321,6 +331,12 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.stepsTable.data = this.convertStepsToData(this.steps);
|
this.stepsTable.data = this.convertStepsToData(this.steps);
|
||||||
|
this.startStepDropdownValues = [];
|
||||||
|
this.steps.forEach((step) => {
|
||||||
|
this.startStepDropdownValues.push({ displayName: step.id + ': ' + step.stepName, name: step.id.toString() });
|
||||||
|
});
|
||||||
|
this.startStepDropdown.values = this.startStepDropdownValues;
|
||||||
|
|
||||||
});
|
});
|
||||||
editStepDialog.openDialog();
|
editStepDialog.openDialog();
|
||||||
}
|
}
|
||||||
@@ -337,30 +353,52 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
delete steps[rowNumber];
|
delete steps[rowNumber];
|
||||||
let data = this.convertStepsToData(steps);
|
let data = this.convertStepsToData(steps);
|
||||||
this.stepsTable.data = data;
|
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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.stepsTable.onRowSelected(() => {
|
this.stepsTable.onRowSelected((row) => {
|
||||||
// only let edit or delete steps if there's
|
// only let edit or delete steps if there's
|
||||||
// one step selection
|
// one step selection
|
||||||
if (this.stepsTable.selectedRows.length === 1) {
|
if (this.stepsTable.selectedRows.length === 1) {
|
||||||
this.moveStepUpButton.enabled = true;
|
let rowNumber = this.stepsTable.selectedRows[0];
|
||||||
this.moveStepDownButton.enabled = true;
|
// if it's not the last step
|
||||||
|
if (this.steps.length !== rowNumber + 1) {
|
||||||
|
this.moveStepDownButton.enabled = true;
|
||||||
|
}
|
||||||
|
// if it's not the first step
|
||||||
|
if (rowNumber !== 0) {
|
||||||
|
this.moveStepUpButton.enabled = true;
|
||||||
|
}
|
||||||
this.deleteStepButton.enabled = true;
|
this.deleteStepButton.enabled = true;
|
||||||
this.editStepButton.enabled = true;
|
this.editStepButton.enabled = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let stepMoveContainer = this.createRowContainer(view).withItems([this.startStepDropdown, this.moveStepUpButton, this.moveStepDownButton]).component();
|
||||||
let formModel = view.modelBuilder.formContainer()
|
let stepsDialogContainer = this.createRowContainer(view).withItems([this.newStepButton, this.editStepButton, this.deleteStepButton]).component();
|
||||||
.withFormItems([{
|
let formModel = view.modelBuilder.formContainer().withFormItems([
|
||||||
|
{
|
||||||
component: this.stepsTable,
|
component: this.stepsTable,
|
||||||
title: this.JobStepsTopLabelString,
|
title: this.JobStepsTopLabelString
|
||||||
actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton]
|
},
|
||||||
}]).withLayout({ width: '100%' }).component();
|
{
|
||||||
|
component: stepMoveContainer,
|
||||||
|
title: this.StartStepDropdownString
|
||||||
|
},
|
||||||
|
{
|
||||||
|
component: stepsDialogContainer,
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
]).withLayout({ width: '100%' }).component();
|
||||||
await view.initializeModel(formModel);
|
await view.initializeModel(formModel);
|
||||||
|
this.setConditionDropdownSelectedValue(this.startStepDropdown, this.model.startStepId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -623,6 +661,7 @@ export class JobDialog extends AgentDialog<JobData> {
|
|||||||
this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown);
|
this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown);
|
||||||
this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown);
|
this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown);
|
||||||
this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown);
|
this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown);
|
||||||
|
this.model.startStepId = +this.getDropdownValue(this.startStepDropdown);
|
||||||
if (!this.model.jobSteps) {
|
if (!this.model.jobSteps) {
|
||||||
this.model.jobSteps = [];
|
this.model.jobSteps = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
|
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
|
||||||
private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
|
private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
|
||||||
|
|
||||||
|
// Event Name strings
|
||||||
|
private readonly NewStepDialog = 'NewStepDialogOpened';
|
||||||
|
private readonly EditStepDialog = 'EditStepDialogOpened';
|
||||||
// UI Components
|
// UI Components
|
||||||
|
|
||||||
// Dialogs
|
// Dialogs
|
||||||
@@ -131,6 +134,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
|||||||
this.jobModel = jobModel;
|
this.jobModel = jobModel;
|
||||||
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
|
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
this.dialogName = this.isEdit ? this.EditStepDialog : this.NewStepDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeUIComponents() {
|
private initializeUIComponents() {
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
|
|||||||
private static readonly AlertEmailColumnLabel: string = localize('createOperator.AlertEmailColumnLabel', 'E-mail');
|
private static readonly AlertEmailColumnLabel: string = localize('createOperator.AlertEmailColumnLabel', 'E-mail');
|
||||||
private static readonly AlertPagerColumnLabel: string = localize('createOperator.AlertPagerColumnLabel', 'Pager');
|
private static readonly AlertPagerColumnLabel: string = localize('createOperator.AlertPagerColumnLabel', 'Pager');
|
||||||
|
|
||||||
|
// Event strings
|
||||||
|
private readonly NewOperatorDialog = 'NewOperatorDialogOpened';
|
||||||
|
private readonly EditOperatorDialog = 'EditOperatorDialogOpened';
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
|
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
@@ -68,12 +72,15 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
|
|||||||
|
|
||||||
// Notification tab controls
|
// Notification tab controls
|
||||||
private alertsTable: sqlops.TableComponent;
|
private alertsTable: sqlops.TableComponent;
|
||||||
|
private isEdit: boolean = false;
|
||||||
|
|
||||||
constructor(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo = undefined) {
|
constructor(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo = undefined) {
|
||||||
super(
|
super(
|
||||||
ownerUri,
|
ownerUri,
|
||||||
new OperatorData(ownerUri, operatorInfo),
|
new OperatorData(ownerUri, operatorInfo),
|
||||||
operatorInfo ? OperatorDialog.EditDialogTitle : OperatorDialog.CreateDialogTitle);
|
operatorInfo ? OperatorDialog.EditDialogTitle : OperatorDialog.CreateDialogTitle);
|
||||||
|
this.isEdit = operatorInfo ? true : false;
|
||||||
|
this.dialogName = this.isEdit ? this.EditOperatorDialog : this.NewOperatorDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
|
|||||||
private static readonly PowerShellLabel: string = localize('createProxy.PowerShell', 'PowerShell');
|
private static readonly PowerShellLabel: string = localize('createProxy.PowerShell', 'PowerShell');
|
||||||
private static readonly SubSystemHeadingLabel: string = localize('createProxy.subSystemHeading', 'Active to the following subsytems');
|
private static readonly SubSystemHeadingLabel: string = localize('createProxy.subSystemHeading', 'Active to the following subsytems');
|
||||||
|
|
||||||
|
private readonly NewProxyDialog = 'NewProxyDialogOpened';
|
||||||
|
private readonly EditProxyDialog = 'EditProxyDialogOpened';
|
||||||
|
|
||||||
// UI Components
|
// UI Components
|
||||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||||
|
|
||||||
@@ -56,6 +59,7 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
|
|||||||
private powershellCheckBox: sqlops.CheckBoxComponent;
|
private powershellCheckBox: sqlops.CheckBoxComponent;
|
||||||
|
|
||||||
private credentials: sqlops.CredentialInfo[];
|
private credentials: sqlops.CredentialInfo[];
|
||||||
|
private isEdit: boolean = false;
|
||||||
|
|
||||||
constructor(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo = undefined, credentials: sqlops.CredentialInfo[]) {
|
constructor(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo = undefined, credentials: sqlops.CredentialInfo[]) {
|
||||||
super(
|
super(
|
||||||
@@ -63,6 +67,8 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
|
|||||||
new ProxyData(ownerUri, proxyInfo),
|
new ProxyData(ownerUri, proxyInfo),
|
||||||
proxyInfo ? ProxyDialog.EditDialogTitle : ProxyDialog.CreateDialogTitle);
|
proxyInfo ? ProxyDialog.EditDialogTitle : ProxyDialog.CreateDialogTitle);
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
|
this.isEdit = proxyInfo ? true : false;
|
||||||
|
this.dialogName = this.isEdit ? this.EditProxyDialog : this.NewProxyDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||||
|
|||||||
@@ -40,13 +40,13 @@ export class MainController {
|
|||||||
public activate(): void {
|
public activate(): void {
|
||||||
vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
|
vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
|
||||||
let dialog = new JobDialog(ownerUri, jobInfo);
|
let dialog = new JobDialog(ownerUri, jobInfo);
|
||||||
dialog.openDialog();
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
});
|
});
|
||||||
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobInfo: sqlops.AgentJobInfo, jobStepInfo: sqlops.AgentJobStepInfo) => {
|
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobInfo: sqlops.AgentJobInfo, jobStepInfo: sqlops.AgentJobStepInfo) => {
|
||||||
AgentUtils.getAgentService().then((agentService) => {
|
AgentUtils.getAgentService().then((agentService) => {
|
||||||
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
|
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
|
||||||
let dialog = new JobStepDialog(ownerUri, server, jobData, jobStepInfo, false);
|
let dialog = new JobStepDialog(ownerUri, server, jobData, jobStepInfo, false);
|
||||||
dialog.openDialog();
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string, jobName: string) => {
|
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string, jobName: string) => {
|
||||||
@@ -57,17 +57,16 @@ export class MainController {
|
|||||||
AgentUtils.getAgentService().then((agentService) => {
|
AgentUtils.getAgentService().then((agentService) => {
|
||||||
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
|
let jobData: JobData = new JobData(ownerUri, jobInfo, agentService);
|
||||||
let dialog = new AlertDialog(ownerUri, jobData, alertInfo, false);
|
let dialog = new AlertDialog(ownerUri, jobData, alertInfo, false);
|
||||||
dialog.openDialog();
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
|
vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
|
||||||
let dialog = new OperatorDialog(ownerUri, operatorInfo);
|
let dialog = new OperatorDialog(ownerUri, operatorInfo);
|
||||||
dialog.openDialog();
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
});
|
});
|
||||||
vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
|
vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
|
||||||
let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
|
let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
|
||||||
dialog.openDialog();
|
dialog.dialogName ? dialog.openDialog(dialog.dialogName) : dialog.openDialog();
|
||||||
MainController.showNotYetImplemented();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "import",
|
"name": "import",
|
||||||
"displayName": "SQL Server Import",
|
"displayName": "SQL Server Import",
|
||||||
"description": "SQL Server Import for Azure Data Studio supports importing CSV or JSON files into SQL Server.",
|
"description": "SQL Server Import for Azure Data Studio supports importing CSV or JSON files into SQL Server.",
|
||||||
"version": "0.4.2",
|
"version": "0.5.0",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||||
"version": "1.5.0-alpha.60",
|
"version": "1.5.0-alpha.63",
|
||||||
"downloadFileNames": {
|
"downloadFileNames": {
|
||||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||||
|
|||||||
17
extensions/notebook/README.md
Normal file
17
extensions/notebook/README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Notebook extension for Azure Data Studio
|
||||||
|
|
||||||
|
Welcome to the Notebook extension for Azure Data Studio! This extension supports core notebook functionality including configuration settings, actions such as New / Open Notebook, and more.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||||
|
|
||||||
|
## Privacy Statement
|
||||||
|
|
||||||
|
The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement) describes the privacy statement of this software.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
|
||||||
|
Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt).
|
||||||
71
extensions/notebook/package.json
Normal file
71
extensions/notebook/package.json
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
{
|
||||||
|
"name": "notebook",
|
||||||
|
"displayName": "%displayName%",
|
||||||
|
"description": "%description%",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"publisher": "Microsoft",
|
||||||
|
"engines": {
|
||||||
|
"vscode": "*",
|
||||||
|
"sqlops": "*"
|
||||||
|
},
|
||||||
|
"main": "./out/extension",
|
||||||
|
"activationEvents": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"contributes": {
|
||||||
|
"configuration": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "%notebook.configuration.title%",
|
||||||
|
"properties": {
|
||||||
|
"notebook.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "%notebook.enabled.description%"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "notebook.command.new",
|
||||||
|
"title": "%notebook.command.new%",
|
||||||
|
"icon": {
|
||||||
|
"dark": "resources/dark/new_notebook_inverse.svg",
|
||||||
|
"light": "resources/light/new_notebook.svg"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "notebook.command.open",
|
||||||
|
"title": "%notebook.command.open%",
|
||||||
|
"icon": {
|
||||||
|
"dark": "resources/dark/open_notebook_inverse.svg",
|
||||||
|
"light": "resources/light/open_notebook.svg"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"menus": {
|
||||||
|
"commandPalette": [
|
||||||
|
{
|
||||||
|
"command": "notebook.command.new",
|
||||||
|
"when": "config.notebook.enabled"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "notebook.command.open",
|
||||||
|
"when": "config.notebook.enabled"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"keybindings": [
|
||||||
|
{
|
||||||
|
"command": "notebook.command.new",
|
||||||
|
"key": "Ctrl+Shift+N",
|
||||||
|
"when": "config.notebook.enabled"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-nls": "^4.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "8.0.33"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
extensions/notebook/package.nls.json
Normal file
8
extensions/notebook/package.nls.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"displayName": "Notebook Core Extensions",
|
||||||
|
"description": "Defines the Data-procotol based Notebook contribution and many Notebook commands and contributions.",
|
||||||
|
"notebook.configuration.title": "Notebook configuration",
|
||||||
|
"notebook.enabled.description": "Enable viewing notebook files using built-in notebook editor.",
|
||||||
|
"notebook.command.new": "New Notebook",
|
||||||
|
"notebook.command.open": "Open Notebook"
|
||||||
|
}
|
||||||
1
extensions/notebook/resources/dark/new_notebook_inverse.svg
Executable file
1
extensions/notebook/resources/dark/new_notebook_inverse.svg
Executable file
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#388a34;}</style></defs><title>new_notebook_inverse</title><path class="cls-1" d="M11.87,1.24V.33H9.13A3.78,3.78,0,0,0,7.92.52a3.48,3.48,0,0,0-1.07.58A3.6,3.6,0,0,0,5.78.52,3.78,3.78,0,0,0,4.57.33H1.83v.91H0V13.1H9.67v-.91H7a4,4,0,0,1,.47-.39A2.39,2.39,0,0,1,8,11.52a2.2,2.2,0,0,1,.53-.18,2.93,2.93,0,0,1,.61-.06h2.74V2.15h.91V9h.91V1.24Zm-9.13,0H4.57a3,3,0,0,1,1,.17,2.58,2.58,0,0,1,.85.49v8.93a3.94,3.94,0,0,0-.88-.35,3.73,3.73,0,0,0-.94-.12H2.74Zm-1.82,11v-10h.91v9.13H4.57a2.93,2.93,0,0,1,.61.06,2.55,2.55,0,0,1,.53.18,2.68,2.68,0,0,1,.49.28,3.29,3.29,0,0,1,.46.39Zm8.21-1.83a3.73,3.73,0,0,0-.94.12,4.22,4.22,0,0,0-.89.35V1.9a2.74,2.74,0,0,1,.86-.49,2.91,2.91,0,0,1,1-.17H11v9.12ZM12.87,10v2.2h-2.2v.91h3V10Z"/><polygon class="cls-2" points="16 12.19 16 13.13 13.8 13.13 13.8 15.33 12.87 15.33 12.87 13.13 10.67 13.13 10.67 12.19 12.87 12.19 12.87 9.99 13.8 9.99 13.8 12.19 16 12.19"/><path class="cls-2" d="M13.8,12.19V10h-.93v2.2h-2.2v.94h2.2v2.2h.93v-2.2H16v-.94Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
1
extensions/notebook/resources/dark/open_notebook_inverse.svg
Executable file
1
extensions/notebook/resources/dark/open_notebook_inverse.svg
Executable file
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#0095d7;}</style></defs><title>open_notebook_inverse</title><path class="cls-1" d="M12.55,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9.18a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.29,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.93,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.67,3.2H2v.9H.15V15.85H13.72V5.48ZM2.86,4.1H4.67a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.86ZM1,15V5H2v9H4.67a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39ZM12.8,15H7.11a2.7,2.7,0,0,1,.47-.39A2.83,2.83,0,0,1,8,14.28a3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9.18,14h2.73V5h.89Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><path class="cls-2" d="M13.19,3.57h0v0Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><polygon class="cls-2" points="14.21 1.65 14.19 1.65 14.19 1.63 14.21 1.65"/><path class="cls-2" d="M15.91,2.1,14.2,3.81l-.38.38-.62-.61v0l1-1H12.79a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47A3.41,3.41,0,0,0,9.5,6.11H8.6a4.68,4.68,0,0,1,.16-1.19A4.74,4.74,0,0,1,9,4.26a2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16,2.79,2.79,0,0,1,.34-.18l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.82,0Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
1
extensions/notebook/resources/light/new_notebook.svg
Executable file
1
extensions/notebook/resources/light/new_notebook.svg
Executable file
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#388a34;}</style></defs><title>new_notebook</title><path d="M11.86,1.24V.33H9.13A3.78,3.78,0,0,0,7.91.52a3.48,3.48,0,0,0-1.07.58A3.6,3.6,0,0,0,5.78.52,3.78,3.78,0,0,0,4.57.33H1.83v.91H0V13.1H9.66v-.91H7a4,4,0,0,1,.47-.39A2.39,2.39,0,0,1,8,11.52a2.2,2.2,0,0,1,.53-.18,2.93,2.93,0,0,1,.61-.06h2.74V2.15h.91V9h.91V1.24Zm-9.13,0H4.57a3,3,0,0,1,1,.17,2.58,2.58,0,0,1,.85.49v8.93a3.94,3.94,0,0,0-.88-.35,3.73,3.73,0,0,0-.94-.12H2.73Zm-1.82,11v-10h.91v9.13H4.57a2.93,2.93,0,0,1,.61.06,2.55,2.55,0,0,1,.53.18,2.68,2.68,0,0,1,.49.28,3.29,3.29,0,0,1,.46.39Zm8.21-1.83a3.73,3.73,0,0,0-.94.12,4.22,4.22,0,0,0-.89.35V1.9a2.74,2.74,0,0,1,.86-.49,2.91,2.91,0,0,1,1-.17h1.82v9.12ZM12.86,10v2.2h-2.2v.91h3V10Z"/><polygon class="cls-1" points="15.99 12.19 15.99 13.13 13.79 13.13 13.79 15.33 12.87 15.33 12.87 13.13 10.66 13.13 10.66 12.19 12.87 12.19 12.87 9.99 13.79 9.99 13.79 12.19 15.99 12.19"/><path class="cls-1" d="M13.79,12.19V10h-.93v2.2h-2.2v.94h2.2v2.2h.93v-2.2H16v-.94Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
1
extensions/notebook/resources/light/open_notebook.svg
Executable file
1
extensions/notebook/resources/light/open_notebook.svg
Executable file
@@ -0,0 +1 @@
|
|||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#00539c;}</style></defs><title>open_notebook</title><path d="M12.4,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.14,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.78,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.52,3.2H1.81v.9H0V15.85H13.57V5.48ZM2.71,4.1H4.52a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.71ZM.9,15V5h.91v9H4.52a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39Zm11.75,0H7a2.7,2.7,0,0,1,.47-.39,2.83,2.83,0,0,1,.47-.29,3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9,14h2.73V5h.89Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><path class="cls-1" d="M13,3.57h0v0Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><polygon class="cls-1" points="14.06 1.65 14.04 1.65 14.04 1.63 14.06 1.65"/><path class="cls-1" d="M15.76,2.1,14,3.81l-.38.38L13,3.58v0l1-1H12.64a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47,3.41,3.41,0,0,0-.27,1.39h-.9a4.68,4.68,0,0,1,.16-1.19,4.74,4.74,0,0,1,.25-.66,2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16A2.79,2.79,0,0,1,11,2.08l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.67,0Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
50
extensions/notebook/src/extension.ts
Normal file
50
extensions/notebook/src/extension.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
export function activate(extensionContext: vscode.ExtensionContext) {
|
||||||
|
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.new', () => {
|
||||||
|
let title = `Untitled-${counter++}`;
|
||||||
|
let untitledUri = vscode.Uri.parse(`untitled:${title}`);
|
||||||
|
sqlops.nb.showNotebookDocument(untitledUri).then(success => {
|
||||||
|
|
||||||
|
}, (err: Error) => {
|
||||||
|
vscode.window.showErrorMessage(err.message);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.open', () => {
|
||||||
|
openNotebook();
|
||||||
|
}));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openNotebook(): Promise<void> {
|
||||||
|
try {
|
||||||
|
let filter = {};
|
||||||
|
// TODO support querying valid notebook file types
|
||||||
|
filter[localize('notebookFiles', 'Notebooks')] = ['ipynb'];
|
||||||
|
let file = await vscode.window.showOpenDialog({
|
||||||
|
filters: filter
|
||||||
|
});
|
||||||
|
if (file) {
|
||||||
|
let doc = await vscode.workspace.openTextDocument(file[0]);
|
||||||
|
vscode.window.showTextDocument(doc);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
vscode.window.showErrorMessage(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this method is called when your extension is deactivated
|
||||||
|
export function deactivate() {
|
||||||
|
}
|
||||||
9
extensions/notebook/src/typings/refs.d.ts
vendored
Normal file
9
extensions/notebook/src/typings/refs.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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/vs/vscode.d.ts'/>
|
||||||
|
/// <reference types='@types/node'/>
|
||||||
22
extensions/notebook/tsconfig.json
Normal file
22
extensions/notebook/tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compileOnSave": true,
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es6",
|
||||||
|
"outDir": "./out",
|
||||||
|
"lib": [
|
||||||
|
"es6", "es2015.promise"
|
||||||
|
],
|
||||||
|
"typeRoots": [
|
||||||
|
"./node_modules/@types"
|
||||||
|
],
|
||||||
|
"sourceMap": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
13
extensions/notebook/yarn.lock
Normal file
13
extensions/notebook/yarn.lock
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@types/node@8.0.33":
|
||||||
|
version "8.0.33"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.33.tgz#1126e94374014e54478092830704f6ea89df04cd"
|
||||||
|
integrity sha512-vmCdO8Bm1ExT+FWfC9sd9r4jwqM7o97gGy2WBshkkXbf/2nLAJQUrZfIhw27yVOtLUev6kSZc4cav/46KbDd8A==
|
||||||
|
|
||||||
|
vscode-nls@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.0.0.tgz#4001c8a6caba5cedb23a9c5ce1090395c0e44002"
|
||||||
|
integrity sha512-qCfdzcH+0LgQnBpZA53bA32kzp9rpq/f66Som577ObeuDlFIrtbEJ+A/+CCxjIh4G8dpJYNCKIsxpRAHIfsbNw==
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "profiler",
|
"name": "profiler",
|
||||||
"displayName": "SQL Server Profiler",
|
"displayName": "SQL Server Profiler",
|
||||||
"description": "SQL Server Profiler for Azure Data Studio",
|
"description": "SQL Server Profiler for Azure Data Studio",
|
||||||
"version": "0.5.1",
|
"version": "0.6.0",
|
||||||
"publisher": "Microsoft",
|
"publisher": "Microsoft",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "azuredatastudio",
|
"name": "azuredatastudio",
|
||||||
"version": "1.3.6",
|
"version": "1.3.7",
|
||||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
@@ -90,6 +90,7 @@
|
|||||||
"@types/mocha": "2.2.39",
|
"@types/mocha": "2.2.39",
|
||||||
"@types/sanitize-html": "^1.18.2",
|
"@types/sanitize-html": "^1.18.2",
|
||||||
"@types/semver": "5.3.30",
|
"@types/semver": "5.3.30",
|
||||||
|
"@types/should": "^13.0.0",
|
||||||
"@types/sinon": "1.16.34",
|
"@types/sinon": "1.16.34",
|
||||||
"@types/winreg": "^1.2.30",
|
"@types/winreg": "^1.2.30",
|
||||||
"asar": "^0.14.0",
|
"asar": "^0.14.0",
|
||||||
@@ -148,8 +149,10 @@
|
|||||||
"queue": "3.0.6",
|
"queue": "3.0.6",
|
||||||
"remap-istanbul": "^0.6.4",
|
"remap-istanbul": "^0.6.4",
|
||||||
"rimraf": "^2.2.8",
|
"rimraf": "^2.2.8",
|
||||||
|
"should": "^13.2.3",
|
||||||
"sinon": "^1.17.2",
|
"sinon": "^1.17.2",
|
||||||
"source-map": "^0.4.4",
|
"source-map": "^0.4.4",
|
||||||
|
"temp-write": "^3.4.0",
|
||||||
"tslint": "^5.9.1",
|
"tslint": "^5.9.1",
|
||||||
"typemoq": "^0.3.2",
|
"typemoq": "^0.3.2",
|
||||||
"typescript": "2.9.2",
|
"typescript": "2.9.2",
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ export const NewQuery = 'NewQuery';
|
|||||||
export const FirewallRuleRequested = 'FirewallRuleCreated';
|
export const FirewallRuleRequested = 'FirewallRuleCreated';
|
||||||
export const DashboardNavigated = 'DashboardNavigated';
|
export const DashboardNavigated = 'DashboardNavigated';
|
||||||
|
|
||||||
|
|
||||||
// Telemetry Properties
|
// Telemetry Properties
|
||||||
|
|
||||||
// Modal Dialogs:
|
// Modal Dialogs:
|
||||||
@@ -42,3 +41,21 @@ export const Accounts = 'Accounts';
|
|||||||
export const FireWallRule = 'FirewallRule';
|
export const FireWallRule = 'FirewallRule';
|
||||||
export const AutoOAuth = 'AutoOAuth';
|
export const AutoOAuth = 'AutoOAuth';
|
||||||
export const AddNewDashboardTab = 'AddNewDashboardTab';
|
export const AddNewDashboardTab = 'AddNewDashboardTab';
|
||||||
|
|
||||||
|
// SQL Agent Events:
|
||||||
|
|
||||||
|
// Views
|
||||||
|
export const JobsView = 'JobsViewOpened';
|
||||||
|
export const JobHistoryView = 'JobHistoryViewOpened';
|
||||||
|
export const JobStepsView = 'JobStepsViewOpened';
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
export const RunAgentJob = 'RunAgentJob';
|
||||||
|
export const StopAgentJob = 'StopAgentJob';
|
||||||
|
export const DeleteAgentJob = 'DeleteAgentJob';
|
||||||
|
export const DeleteAgentJobStep = 'DeleteAgentJobStep';
|
||||||
|
export const DeleteAgentAlert = 'DeleteAgentAlert';
|
||||||
|
export const DeleteAgentOperator = 'DeleteAgentOperator';
|
||||||
|
export const DeleteAgentProxy = 'DeleteAgentProxy';
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,31 +4,10 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
import * as crypto from 'crypto';
|
|
||||||
import * as os from 'os';
|
|
||||||
import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService, ITelemetryData } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||||
import { warn } from 'sql/base/common/log';
|
import { warn } from 'sql/base/common/log';
|
||||||
|
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
|
||||||
|
|
||||||
// Generate a unique, deterministic ID for the current user of the extension
|
|
||||||
export function generateUserId(): Promise<string> {
|
|
||||||
return new Promise<string>(resolve => {
|
|
||||||
try {
|
|
||||||
getmac.getMac((error, macAddress) => {
|
|
||||||
if (!error) {
|
|
||||||
resolve(crypto.createHash('sha256').update(macAddress + os.homedir(), 'utf8').digest('hex'));
|
|
||||||
} else {
|
|
||||||
resolve(generateUuid()); // fallback
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
resolve(generateUuid()); // fallback
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IConnectionTelemetryData extends ITelemetryData {
|
export interface IConnectionTelemetryData extends ITelemetryData {
|
||||||
provider?: string;
|
provider?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
|||||||
import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput';
|
import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput';
|
||||||
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
|
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
|
||||||
import { DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
import { DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
||||||
|
import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
|
||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
@@ -183,17 +184,6 @@ function getNotebookFileExtensions() {
|
|||||||
return notebookRegistry.getSupportedFileExtensions();
|
return notebookRegistry.getSupportedFileExtensions();
|
||||||
}
|
}
|
||||||
|
|
||||||
function getProviderForFileName(fileName: string) {
|
|
||||||
let fileExt = path.extname(fileName);
|
|
||||||
if (fileExt && fileExt.startsWith('.')) {
|
|
||||||
fileExt = fileExt.slice(1,fileExt.length);
|
|
||||||
let notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
|
|
||||||
return notebookRegistry.getProviderForFileType(fileExt);
|
|
||||||
}
|
|
||||||
return DEFAULT_NOTEBOOK_PROVIDER;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the given EditorInput is set to either undefined or sql mode
|
* Checks whether the given EditorInput is set to either undefined or sql mode
|
||||||
* @param input The EditorInput to check the mode of
|
* @param input The EditorInput to check the mode of
|
||||||
|
|||||||
@@ -769,8 +769,19 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let tokens = await this._accountManagementService.getSecurityToken(account, AzureResource.Sql);
|
let tokensByTenant = await this._accountManagementService.getSecurityToken(account, AzureResource.Sql);
|
||||||
connection.options['azureAccountToken'] = Object.values(tokens)[0].token;
|
let token: string;
|
||||||
|
let tenantId = connection.azureTenantId;
|
||||||
|
if (tenantId && tokensByTenant[tenantId]) {
|
||||||
|
token = tokensByTenant[tenantId].token;
|
||||||
|
} else {
|
||||||
|
let tokens = Object.values(tokensByTenant);
|
||||||
|
if (tokens.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
token = Object.values(tokensByTenant)[0].token;
|
||||||
|
}
|
||||||
|
connection.options['azureAccountToken'] = token;
|
||||||
connection.options['password'] = '';
|
connection.options['password'] = '';
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
|
|||||||
this.savePassword = model.savePassword;
|
this.savePassword = model.savePassword;
|
||||||
this.saveProfile = model.saveProfile;
|
this.saveProfile = model.saveProfile;
|
||||||
this._id = model.id;
|
this._id = model.id;
|
||||||
|
this.azureTenantId = model.azureTenantId;
|
||||||
} else {
|
} else {
|
||||||
//Default for a new connection
|
//Default for a new connection
|
||||||
this.savePassword = false;
|
this.savePassword = false;
|
||||||
@@ -84,6 +85,14 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
|
|||||||
this._id = value;
|
this._id = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get azureTenantId(): string {
|
||||||
|
return this.options['azureTenantId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public set azureTenantId(value: string) {
|
||||||
|
this.options['azureTenantId'] = value;
|
||||||
|
}
|
||||||
|
|
||||||
public get groupFullName(): string {
|
public get groupFullName(): string {
|
||||||
return this._groupName;
|
return this._groupName;
|
||||||
}
|
}
|
||||||
@@ -159,7 +168,8 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
|
|||||||
userName: this.userName,
|
userName: this.userName,
|
||||||
options: this.options,
|
options: this.options,
|
||||||
saveProfile: this.saveProfile,
|
saveProfile: this.saveProfile,
|
||||||
id: this.id
|
id: this.id,
|
||||||
|
azureTenantId: this.azureTenantId
|
||||||
};
|
};
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -52,9 +52,11 @@ export class ConnectionWidget {
|
|||||||
private _password: string;
|
private _password: string;
|
||||||
private _rememberPasswordCheckBox: Checkbox;
|
private _rememberPasswordCheckBox: Checkbox;
|
||||||
private _azureAccountDropdown: SelectBox;
|
private _azureAccountDropdown: SelectBox;
|
||||||
|
private _azureTenantDropdown: SelectBox;
|
||||||
private _refreshCredentialsLinkBuilder: Builder;
|
private _refreshCredentialsLinkBuilder: Builder;
|
||||||
private _addAzureAccountMessage: string = localize('connectionWidget.AddAzureAccount', 'Add an account...');
|
private _addAzureAccountMessage: string = localize('connectionWidget.AddAzureAccount', 'Add an account...');
|
||||||
private readonly _azureProviderId = 'azurePublicCloud';
|
private readonly _azureProviderId = 'azurePublicCloud';
|
||||||
|
private _azureTenantId: string;
|
||||||
private _azureAccountList: sqlops.Account[];
|
private _azureAccountList: sqlops.Account[];
|
||||||
private _advancedButton: Button;
|
private _advancedButton: Button;
|
||||||
private _callbacks: IConnectionComponentCallbacks;
|
private _callbacks: IConnectionComponentCallbacks;
|
||||||
@@ -215,6 +217,12 @@ export class ConnectionWidget {
|
|||||||
let refreshCredentialsBuilder = DialogHelper.appendRow(this._tableContainer, '', 'connection-label', 'connection-input', 'azure-account-row refresh-credentials-link');
|
let refreshCredentialsBuilder = DialogHelper.appendRow(this._tableContainer, '', 'connection-label', 'connection-input', 'azure-account-row refresh-credentials-link');
|
||||||
this._refreshCredentialsLinkBuilder = refreshCredentialsBuilder.a({ href: '#' }).text(localize('connectionWidget.refreshAzureCredentials', 'Refresh account credentials'));
|
this._refreshCredentialsLinkBuilder = refreshCredentialsBuilder.a({ href: '#' }).text(localize('connectionWidget.refreshAzureCredentials', 'Refresh account credentials'));
|
||||||
|
|
||||||
|
// Azure tenant picker
|
||||||
|
let tenantLabel = localize('connection.azureTenantDropdownLabel', 'Azure AD tenant');
|
||||||
|
let tenantDropdownBuilder = DialogHelper.appendRow(this._tableContainer, tenantLabel, 'connection-label', 'connection-input', 'azure-account-row azure-tenant-row');
|
||||||
|
this._azureTenantDropdown = new SelectBox([], undefined, this._contextViewService, tenantDropdownBuilder.getContainer(), { ariaLabel: tenantLabel });
|
||||||
|
DialogHelper.appendInputSelectBox(tenantDropdownBuilder, this._azureTenantDropdown);
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
let databaseOption = this._optionsMaps[ConnectionOptionSpecialType.databaseName];
|
let databaseOption = this._optionsMaps[ConnectionOptionSpecialType.databaseName];
|
||||||
let databaseNameBuilder = DialogHelper.appendRow(this._tableContainer, databaseOption.displayName, 'connection-label', 'connection-input');
|
let databaseNameBuilder = DialogHelper.appendRow(this._tableContainer, databaseOption.displayName, 'connection-label', 'connection-input');
|
||||||
@@ -308,6 +316,13 @@ export class ConnectionWidget {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._azureTenantDropdown) {
|
||||||
|
this._toDispose.push(styler.attachSelectBoxStyler(this._azureTenantDropdown, this._themeService));
|
||||||
|
this._toDispose.push(this._azureTenantDropdown.onDidSelect((selectInfo) => {
|
||||||
|
this.onAzureTenantSelected(selectInfo.index);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (this._refreshCredentialsLinkBuilder) {
|
if (this._refreshCredentialsLinkBuilder) {
|
||||||
this._toDispose.push(this._refreshCredentialsLinkBuilder.on(DOM.EventType.CLICK, async () => {
|
this._toDispose.push(this._refreshCredentialsLinkBuilder.on(DOM.EventType.CLICK, async () => {
|
||||||
let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
|
let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
|
||||||
@@ -426,7 +441,7 @@ export class ConnectionWidget {
|
|||||||
accountDropdownOptions.push(this._addAzureAccountMessage);
|
accountDropdownOptions.push(this._addAzureAccountMessage);
|
||||||
this._azureAccountDropdown.setOptions(accountDropdownOptions);
|
this._azureAccountDropdown.setOptions(accountDropdownOptions);
|
||||||
this._azureAccountDropdown.selectWithOptionName(oldSelection);
|
this._azureAccountDropdown.selectWithOptionName(oldSelection);
|
||||||
this.updateRefreshCredentialsLink();
|
await this.onAzureAccountSelected();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateRefreshCredentialsLink(): Promise<void> {
|
private async updateRefreshCredentialsLink(): Promise<void> {
|
||||||
@@ -441,7 +456,6 @@ export class ConnectionWidget {
|
|||||||
private async onAzureAccountSelected(): Promise<void> {
|
private async onAzureAccountSelected(): Promise<void> {
|
||||||
// Reset the dropdown's validation message if the old selection was not valid but the new one is
|
// Reset the dropdown's validation message if the old selection was not valid but the new one is
|
||||||
this.validateAzureAccountSelection(false);
|
this.validateAzureAccountSelection(false);
|
||||||
this._refreshCredentialsLinkBuilder.display('none');
|
|
||||||
|
|
||||||
// Open the add account dialog if needed, then select the added account
|
// Open the add account dialog if needed, then select the added account
|
||||||
if (this._azureAccountDropdown.value === this._addAzureAccountMessage) {
|
if (this._azureAccountDropdown.value === this._addAzureAccountMessage) {
|
||||||
@@ -461,6 +475,35 @@ export class ConnectionWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.updateRefreshCredentialsLink();
|
this.updateRefreshCredentialsLink();
|
||||||
|
|
||||||
|
// Display the tenant select box if needed
|
||||||
|
const hideTenantsClassName = 'hide-azure-tenants';
|
||||||
|
let selectedAccount = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
|
||||||
|
if (selectedAccount && selectedAccount.properties.tenants && selectedAccount.properties.tenants.length > 1) {
|
||||||
|
// There are multiple tenants available so let the user select one
|
||||||
|
let options = selectedAccount.properties.tenants.map(tenant => tenant.displayName);
|
||||||
|
this._azureTenantDropdown.setOptions(options);
|
||||||
|
this._tableContainer.getContainer().classList.remove(hideTenantsClassName);
|
||||||
|
this.onAzureTenantSelected(0);
|
||||||
|
} else {
|
||||||
|
if (selectedAccount && selectedAccount.properties.tenants && selectedAccount.properties.tenants.length === 1) {
|
||||||
|
this._azureTenantId = selectedAccount.properties.tenants[0].id;
|
||||||
|
} else {
|
||||||
|
this._azureTenantId = undefined;
|
||||||
|
}
|
||||||
|
this._tableContainer.getContainer().classList.add(hideTenantsClassName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onAzureTenantSelected(tenantIndex: number): void {
|
||||||
|
this._azureTenantId = undefined;
|
||||||
|
let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
|
||||||
|
if (account && account.properties.tenants) {
|
||||||
|
let tenant = account.properties.tenants[tenantIndex];
|
||||||
|
if (tenant) {
|
||||||
|
this._azureTenantId = tenant.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private serverNameChanged(serverName: string) {
|
private serverNameChanged(serverName: string) {
|
||||||
@@ -518,6 +561,7 @@ export class ConnectionWidget {
|
|||||||
this._passwordInputBox.value = connectionInfo.password ? Constants.passwordChars : '';
|
this._passwordInputBox.value = connectionInfo.password ? Constants.passwordChars : '';
|
||||||
this._password = this.getModelValue(connectionInfo.password);
|
this._password = this.getModelValue(connectionInfo.password);
|
||||||
this._saveProfile = connectionInfo.saveProfile;
|
this._saveProfile = connectionInfo.saveProfile;
|
||||||
|
this._azureTenantId = connectionInfo.azureTenantId;
|
||||||
let groupName: string;
|
let groupName: string;
|
||||||
if (this._saveProfile) {
|
if (this._saveProfile) {
|
||||||
if (!connectionInfo.groupFullName) {
|
if (!connectionInfo.groupFullName) {
|
||||||
@@ -551,6 +595,22 @@ export class ConnectionWidget {
|
|||||||
tableContainerElement.classList.add('hide-azure-accounts');
|
tableContainerElement.classList.add('hide-azure-accounts');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.authType === AuthenticationType.AzureMFA) {
|
||||||
|
this.fillInAzureAccountOptions().then(async () => {
|
||||||
|
this._azureAccountDropdown.selectWithOptionName(this.getModelValue(connectionInfo.userName));
|
||||||
|
await this.onAzureAccountSelected();
|
||||||
|
let tenantId = connectionInfo.azureTenantId;
|
||||||
|
let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
|
||||||
|
if (account && account.properties.tenants.length > 1) {
|
||||||
|
let tenant = account.properties.tenants.find(tenant => tenant.id === tenantId);
|
||||||
|
if (tenant) {
|
||||||
|
this._azureTenantDropdown.selectWithOptionName(tenant.displayName);
|
||||||
|
}
|
||||||
|
this.onAzureTenantSelected(this._azureTenantDropdown.values.indexOf(this._azureTenantDropdown.value));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Disable connect button if -
|
// Disable connect button if -
|
||||||
// 1. Authentication type is SQL Login and no username is provided
|
// 1. Authentication type is SQL Login and no username is provided
|
||||||
// 2. No server name is provided
|
// 2. No server name is provided
|
||||||
@@ -716,6 +776,9 @@ export class ConnectionWidget {
|
|||||||
model.saveProfile = true;
|
model.saveProfile = true;
|
||||||
model.groupId = this.findGroupId(model.groupFullName);
|
model.groupId = this.findGroupId(model.groupFullName);
|
||||||
}
|
}
|
||||||
|
if (this.authType === AuthenticationType.AzureMFA) {
|
||||||
|
model.azureTenantId = this._azureTenantId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return validInputs;
|
return validInputs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,3 +128,7 @@
|
|||||||
.hide-refresh-link .azure-account-row.refresh-credentials-link {
|
.hide-refresh-link .azure-account-row.refresh-credentials-link {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hide-azure-tenants .azure-tenant-row {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ import { AlertsViewComponent } from 'sql/parts/jobManagement/views/alertsView.co
|
|||||||
import { OperatorsViewComponent } from 'sql/parts/jobManagement/views/operatorsView.component';
|
import { OperatorsViewComponent } from 'sql/parts/jobManagement/views/operatorsView.component';
|
||||||
import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component';
|
import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||||
|
import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||||
|
|
||||||
export enum JobActions {
|
export enum JobActions {
|
||||||
Run = 'run',
|
Run = 'run',
|
||||||
@@ -80,7 +83,8 @@ export class RunJobAction extends Action {
|
|||||||
constructor(
|
constructor(
|
||||||
@INotificationService private notificationService: INotificationService,
|
@INotificationService private notificationService: INotificationService,
|
||||||
@IJobManagementService private jobManagementService: IJobManagementService,
|
@IJobManagementService private jobManagementService: IJobManagementService,
|
||||||
@IInstantiationService private instantationService: IInstantiationService
|
@IInstantiationService private instantationService: IInstantiationService,
|
||||||
|
@ITelemetryService private telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(RunJobAction.ID, RunJobAction.LABEL, 'runJobIcon');
|
super(RunJobAction.ID, RunJobAction.LABEL, 'runJobIcon');
|
||||||
}
|
}
|
||||||
@@ -89,6 +93,7 @@ export class RunJobAction extends Action {
|
|||||||
let jobName = context.agentJobInfo.name;
|
let jobName = context.agentJobInfo.name;
|
||||||
let ownerUri = context.ownerUri;
|
let ownerUri = context.ownerUri;
|
||||||
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
|
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
|
||||||
|
this.telemetryService.publicLog(TelemetryKeys.RunAgentJob);
|
||||||
return new TPromise<boolean>((resolve, reject) => {
|
return new TPromise<boolean>((resolve, reject) => {
|
||||||
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Run).then(result => {
|
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Run).then(result => {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -118,7 +123,8 @@ export class StopJobAction extends Action {
|
|||||||
constructor(
|
constructor(
|
||||||
@INotificationService private notificationService: INotificationService,
|
@INotificationService private notificationService: INotificationService,
|
||||||
@IJobManagementService private jobManagementService: IJobManagementService,
|
@IJobManagementService private jobManagementService: IJobManagementService,
|
||||||
@IInstantiationService private instantationService: IInstantiationService
|
@IInstantiationService private instantationService: IInstantiationService,
|
||||||
|
@ITelemetryService private telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(StopJobAction.ID, StopJobAction.LABEL, 'stopJobIcon');
|
super(StopJobAction.ID, StopJobAction.LABEL, 'stopJobIcon');
|
||||||
}
|
}
|
||||||
@@ -127,6 +133,7 @@ export class StopJobAction extends Action {
|
|||||||
let jobName = context.agentJobInfo.name;
|
let jobName = context.agentJobInfo.name;
|
||||||
let ownerUri = context.ownerUri;
|
let ownerUri = context.ownerUri;
|
||||||
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
|
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
|
||||||
|
this.telemetryService.publicLog(TelemetryKeys.StopAgentJob);
|
||||||
return new TPromise<boolean>((resolve, reject) => {
|
return new TPromise<boolean>((resolve, reject) => {
|
||||||
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Stop).then(result => {
|
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Stop).then(result => {
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -174,7 +181,8 @@ export class DeleteJobAction extends Action {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@INotificationService private _notificationService: INotificationService,
|
@INotificationService private _notificationService: INotificationService,
|
||||||
@IJobManagementService private _jobService: IJobManagementService
|
@IJobManagementService private _jobService: IJobManagementService,
|
||||||
|
@ITelemetryService private _telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(DeleteJobAction.ID, DeleteJobAction.LABEL);
|
super(DeleteJobAction.ID, DeleteJobAction.LABEL);
|
||||||
}
|
}
|
||||||
@@ -188,6 +196,7 @@ export class DeleteJobAction extends Action {
|
|||||||
[{
|
[{
|
||||||
label: DeleteJobAction.LABEL,
|
label: DeleteJobAction.LABEL,
|
||||||
run: () => {
|
run: () => {
|
||||||
|
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentJob);
|
||||||
self._jobService.deleteJob(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
self._jobService.deleteJob(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
let errorMessage = nls.localize("jobaction.failedToDeleteJob", "Could not delete job '{0}'.\nError: {1}",
|
let errorMessage = nls.localize("jobaction.failedToDeleteJob", "Could not delete job '{0}'.\nError: {1}",
|
||||||
@@ -234,7 +243,8 @@ export class DeleteStepAction extends Action {
|
|||||||
constructor(
|
constructor(
|
||||||
@INotificationService private _notificationService: INotificationService,
|
@INotificationService private _notificationService: INotificationService,
|
||||||
@IJobManagementService private _jobService: IJobManagementService,
|
@IJobManagementService private _jobService: IJobManagementService,
|
||||||
@IInstantiationService private instantationService: IInstantiationService
|
@IInstantiationService private instantationService: IInstantiationService,
|
||||||
|
@ITelemetryService private _telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(DeleteStepAction.ID, DeleteStepAction.LABEL);
|
super(DeleteStepAction.ID, DeleteStepAction.LABEL);
|
||||||
}
|
}
|
||||||
@@ -249,6 +259,7 @@ export class DeleteStepAction extends Action {
|
|||||||
[{
|
[{
|
||||||
label: DeleteStepAction.LABEL,
|
label: DeleteStepAction.LABEL,
|
||||||
run: () => {
|
run: () => {
|
||||||
|
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentJobStep);
|
||||||
self._jobService.deleteJobStep(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
self._jobService.deleteJobStep(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
let errorMessage = nls.localize("jobaction.failedToDeleteStep", "Could not delete step '{0}'.\nError: {1}",
|
let errorMessage = nls.localize("jobaction.failedToDeleteStep", "Could not delete step '{0}'.\nError: {1}",
|
||||||
@@ -318,7 +329,8 @@ export class DeleteAlertAction extends Action {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@INotificationService private _notificationService: INotificationService,
|
@INotificationService private _notificationService: INotificationService,
|
||||||
@IJobManagementService private _jobService: IJobManagementService
|
@IJobManagementService private _jobService: IJobManagementService,
|
||||||
|
@ITelemetryService private _telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(DeleteAlertAction.ID, DeleteAlertAction.LABEL);
|
super(DeleteAlertAction.ID, DeleteAlertAction.LABEL);
|
||||||
}
|
}
|
||||||
@@ -332,6 +344,7 @@ export class DeleteAlertAction extends Action {
|
|||||||
[{
|
[{
|
||||||
label: DeleteAlertAction.LABEL,
|
label: DeleteAlertAction.LABEL,
|
||||||
run: () => {
|
run: () => {
|
||||||
|
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentAlert);
|
||||||
self._jobService.deleteAlert(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
self._jobService.deleteAlert(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
let errorMessage = nls.localize("jobaction.failedToDeleteAlert", "Could not delete alert '{0}'.\nError: {1}",
|
let errorMessage = nls.localize("jobaction.failedToDeleteAlert", "Could not delete alert '{0}'.\nError: {1}",
|
||||||
@@ -397,7 +410,8 @@ export class DeleteOperatorAction extends Action {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@INotificationService private _notificationService: INotificationService,
|
@INotificationService private _notificationService: INotificationService,
|
||||||
@IJobManagementService private _jobService: IJobManagementService
|
@IJobManagementService private _jobService: IJobManagementService,
|
||||||
|
@ITelemetryService private _telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(DeleteOperatorAction.ID, DeleteOperatorAction.LABEL);
|
super(DeleteOperatorAction.ID, DeleteOperatorAction.LABEL);
|
||||||
}
|
}
|
||||||
@@ -411,6 +425,7 @@ export class DeleteOperatorAction extends Action {
|
|||||||
[{
|
[{
|
||||||
label: DeleteOperatorAction.LABEL,
|
label: DeleteOperatorAction.LABEL,
|
||||||
run: () => {
|
run: () => {
|
||||||
|
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentOperator);
|
||||||
self._jobService.deleteOperator(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
self._jobService.deleteOperator(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
let errorMessage = nls.localize("jobaction.failedToDeleteOperator", "Could not delete operator '{0}'.\nError: {1}",
|
let errorMessage = nls.localize("jobaction.failedToDeleteOperator", "Could not delete operator '{0}'.\nError: {1}",
|
||||||
@@ -477,7 +492,8 @@ export class DeleteProxyAction extends Action {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@INotificationService private _notificationService: INotificationService,
|
@INotificationService private _notificationService: INotificationService,
|
||||||
@IJobManagementService private _jobService: IJobManagementService
|
@IJobManagementService private _jobService: IJobManagementService,
|
||||||
|
@ITelemetryService private _telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(DeleteProxyAction.ID, DeleteProxyAction.LABEL);
|
super(DeleteProxyAction.ID, DeleteProxyAction.LABEL);
|
||||||
}
|
}
|
||||||
@@ -491,6 +507,7 @@ export class DeleteProxyAction extends Action {
|
|||||||
[{
|
[{
|
||||||
label: DeleteProxyAction.LABEL,
|
label: DeleteProxyAction.LABEL,
|
||||||
run: () => {
|
run: () => {
|
||||||
|
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentProxy);
|
||||||
self._jobService.deleteProxy(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
self._jobService.deleteProxy(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
||||||
if (!result || !result.success) {
|
if (!result || !result.success) {
|
||||||
let errorMessage = nls.localize("jobaction.failedToDeleteProxy", "Could not delete proxy '{0}'.\nError: {1}",
|
let errorMessage = nls.localize("jobaction.failedToDeleteProxy", "Could not delete proxy '{0}'.\nError: {1}",
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
<!-- Job History details -->
|
<!-- Job History details -->
|
||||||
<div class='history-details'>
|
<div class='history-details'>
|
||||||
<!-- Previous run list -->
|
<!-- Previous run list -->
|
||||||
<div class="prev-run-list-container" style="min-width: 275px; height: 75vh">
|
<div class="prev-run-list-container" style="min-width: 250px">
|
||||||
<table *ngIf="_showPreviousRuns === true">
|
<table *ngIf="_showPreviousRuns === true">
|
||||||
<tr>
|
<tr>
|
||||||
<td class="date-column">
|
<td class="date-column">
|
||||||
@@ -89,7 +89,9 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<h3 *ngIf="_showPreviousRuns === false" style="text-align: center">No Previous Runs Available</h3>
|
<h3 *ngIf="_showPreviousRuns === false" style="text-align: center">No Previous Runs Available</h3>
|
||||||
<div #table class="step-table prev-run-list" style="position: relative; height: 100%; width: 100%"></div>
|
<div class="step-table prev-run-list" style="position: relative; width: 100%">
|
||||||
|
<div #table style="position: absolute; width: 100%; height: 100%"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Job Steps -->
|
<!-- Job Steps -->
|
||||||
<div class="job-steps" id="job-steps">
|
<div class="job-steps" id="job-steps">
|
||||||
@@ -154,8 +156,8 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<div #jobsteps style="height: 100%">
|
<div #jobsteps style="flex: 1 1 auto; position: relative">
|
||||||
<jobstepsview-component *ngIf="showSteps === true"></jobstepsview-component>
|
<jobstepsview-component *ngIf="showSteps === true" style="position: absolute; height: 100%; width: 100%"></jobstepsview-component>
|
||||||
</div>
|
</div>
|
||||||
<h3 *ngIf="showSteps === false">No Steps Available</h3>
|
<h3 *ngIf="showSteps === false">No Steps Available</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,13 +23,14 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work
|
|||||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
|
||||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { JobManagementView } from 'sql/parts/jobManagement/views/jobManagementView';
|
import { JobManagementView } from 'sql/parts/jobManagement/views/jobManagementView';
|
||||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||||
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
|
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||||
|
|
||||||
export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
|
export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
|
||||||
|
|
||||||
@@ -77,7 +78,8 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
|||||||
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
|
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
|
||||||
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
|
||||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||||
@Inject(IDashboardService) dashboardService: IDashboardService
|
@Inject(IDashboardService) dashboardService: IDashboardService,
|
||||||
|
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService);
|
super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService);
|
||||||
this._treeController = new JobHistoryController();
|
this._treeController = new JobHistoryController();
|
||||||
@@ -141,9 +143,9 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
|||||||
renderer: this._treeRenderer
|
renderer: this._treeRenderer
|
||||||
}, {verticalScrollMode: ScrollbarVisibility.Visible});
|
}, {verticalScrollMode: ScrollbarVisibility.Visible});
|
||||||
this._register(attachListStyler(this._tree, this.themeService));
|
this._register(attachListStyler(this._tree, this.themeService));
|
||||||
this._tree.layout(JobHistoryComponent.INITIAL_TREE_HEIGHT);
|
this._tree.layout(dom.getContentHeight(this._tableContainer.nativeElement));
|
||||||
this.initActionBar();
|
this.initActionBar();
|
||||||
|
this._telemetryService.publicLog(TelemetryKeys.JobHistoryView);
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadHistory() {
|
private loadHistory() {
|
||||||
@@ -293,6 +295,7 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
|||||||
if (historyDetails && statusBar) {
|
if (historyDetails && statusBar) {
|
||||||
let historyBottom = historyDetails.getBoundingClientRect().bottom;
|
let historyBottom = historyDetails.getBoundingClientRect().bottom;
|
||||||
let statusTop = statusBar.getBoundingClientRect().top;
|
let statusTop = statusBar.getBoundingClientRect().top;
|
||||||
|
|
||||||
let height: number = statusTop - historyBottom - JobHistoryComponent.HEADING_HEIGHT;
|
let height: number = statusTop - historyBottom - JobHistoryComponent.HEADING_HEIGHT;
|
||||||
|
|
||||||
if (this._table) {
|
if (this._table) {
|
||||||
@@ -302,14 +305,7 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._tree) {
|
if (this._tree) {
|
||||||
this._tree.layout(height);
|
this._tree.layout(dom.getContentHeight(this._tableContainer.nativeElement));
|
||||||
}
|
|
||||||
|
|
||||||
if (this._jobStepsView) {
|
|
||||||
let element = this._jobStepsView.nativeElement as HTMLElement;
|
|
||||||
if (element) {
|
|
||||||
element.style.height = height + 'px';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,17 +177,17 @@ table.step-list tr.step-row td {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.history-details {
|
.history-details {
|
||||||
height: 100%;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-details > .job-steps {
|
.history-details > .job-steps {
|
||||||
display: block;
|
flex: 1 1 auto;
|
||||||
|
display: flex;
|
||||||
border-left: 3px solid #f4f4f4;
|
border-left: 3px solid #f4f4f4;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
height: 100%;
|
flex-direction: column;
|
||||||
width: 90%;
|
width: 100%;
|
||||||
overflow-y: scroll;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vs-dark .history-details > .job-steps {
|
.vs-dark .history-details > .job-steps {
|
||||||
@@ -241,13 +241,22 @@ table.step-list tr.step-row td {
|
|||||||
width: 140px;
|
width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.steps-tree .monaco-tree .monaco-tree-row {
|
.step-table {
|
||||||
white-space: normal;
|
flex: 1 1 auto;
|
||||||
min-height: 40px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jobhistory-component .jobhistory-heading-container {
|
.prev-run-list-container {
|
||||||
display: -webkit-box;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
jobhistory-component {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
jobhistory-component > .jobhistory-heading-container {
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
jobhistory-component > .jobhistory-heading-container > .icon.in-progress {
|
jobhistory-component > .jobhistory-heading-container > .icon.in-progress {
|
||||||
|
|||||||
@@ -22,4 +22,6 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
<div class='steps-tree' #table style="height: 100%; width: 100%"></div>
|
<div class='steps-tree' style="flex: 1 1 auto; position: relative">
|
||||||
|
<div #table style="position: absolute; height: 100%; width: 100%" ></div>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import 'vs/css!./jobStepsView';
|
import 'vs/css!./jobStepsView';
|
||||||
|
|
||||||
|
import * as dom from 'vs/base/browser/dom';
|
||||||
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable, AfterContentChecked } from '@angular/core';
|
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable, AfterContentChecked } from '@angular/core';
|
||||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||||
@@ -20,7 +21,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
|||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
|
||||||
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||||
|
|
||||||
export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component';
|
export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component';
|
||||||
|
|
||||||
@@ -36,7 +38,6 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
|
|||||||
private _treeDataSource = new JobStepsViewDataSource();
|
private _treeDataSource = new JobStepsViewDataSource();
|
||||||
private _treeRenderer = new JobStepsViewRenderer();
|
private _treeRenderer = new JobStepsViewRenderer();
|
||||||
private _treeFilter = new JobStepsViewFilter();
|
private _treeFilter = new JobStepsViewFilter();
|
||||||
private _pageSize = 1024;
|
|
||||||
|
|
||||||
@ViewChild('table') private _tableContainer: ElementRef;
|
@ViewChild('table') private _tableContainer: ElementRef;
|
||||||
|
|
||||||
@@ -49,7 +50,8 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
|
|||||||
@Inject(IInstantiationService) instantiationService: IInstantiationService,
|
@Inject(IInstantiationService) instantiationService: IInstantiationService,
|
||||||
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
||||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||||
@Inject(IDashboardService) dashboardService: IDashboardService
|
@Inject(IDashboardService) dashboardService: IDashboardService,
|
||||||
|
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService);
|
super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService);
|
||||||
}
|
}
|
||||||
@@ -57,17 +59,8 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
|
|||||||
ngAfterContentChecked() {
|
ngAfterContentChecked() {
|
||||||
if (this._jobHistoryComponent.stepRows.length > 0) {
|
if (this._jobHistoryComponent.stepRows.length > 0) {
|
||||||
this._treeDataSource.data = this._jobHistoryComponent.stepRows;
|
this._treeDataSource.data = this._jobHistoryComponent.stepRows;
|
||||||
if (!this._tree) {
|
|
||||||
this._tree = new Tree(this._tableContainer.nativeElement, {
|
|
||||||
controller: this._treeController,
|
|
||||||
dataSource: this._treeDataSource,
|
|
||||||
filter: this._treeFilter,
|
|
||||||
renderer: this._treeRenderer
|
|
||||||
}, { verticalScrollMode: ScrollbarVisibility.Visible });
|
|
||||||
this._register(attachListStyler(this._tree, this.themeService));
|
|
||||||
}
|
|
||||||
this._tree.layout(this._pageSize);
|
|
||||||
this._tree.setInput(new JobStepsViewModel());
|
this._tree.setInput(new JobStepsViewModel());
|
||||||
|
this.layout();
|
||||||
$('jobstepsview-component .steps-tree .monaco-tree').attr('tabIndex', '-1');
|
$('jobstepsview-component .steps-tree .monaco-tree').attr('tabIndex', '-1');
|
||||||
$('jobstepsview-component .steps-tree .monaco-tree-row').attr('tabIndex', '0');
|
$('jobstepsview-component .steps-tree .monaco-tree-row').attr('tabIndex', '0');
|
||||||
}
|
}
|
||||||
@@ -79,14 +72,20 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
|
|||||||
dataSource: this._treeDataSource,
|
dataSource: this._treeDataSource,
|
||||||
filter: this._treeFilter,
|
filter: this._treeFilter,
|
||||||
renderer: this._treeRenderer
|
renderer: this._treeRenderer
|
||||||
}, {verticalScrollMode: ScrollbarVisibility.Visible});
|
}, {verticalScrollMode: ScrollbarVisibility.Visible, horizontalScrollMode: ScrollbarVisibility.Visible });
|
||||||
|
this.layout();
|
||||||
this._register(attachListStyler(this._tree, this.themeService));
|
this._register(attachListStyler(this._tree, this.themeService));
|
||||||
|
this._telemetryService.publicLog(TelemetryKeys.JobStepsView);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onFirstVisible() {
|
public onFirstVisible() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public layout() {
|
public layout() {
|
||||||
|
if (this._tree) {
|
||||||
|
let treeheight = dom.getContentHeight(this._tableContainer.nativeElement);
|
||||||
|
this._tree.layout(treeheight);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,5 +78,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
jobstepsview-component {
|
jobstepsview-component {
|
||||||
padding-top: 10px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { $ } from 'vs/base/browser/builder';
|
||||||
import * as tree from 'vs/base/parts/tree/browser/tree';
|
import * as tree from 'vs/base/parts/tree/browser/tree';
|
||||||
import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
|
||||||
import { Promise, TPromise } from 'vs/base/common/winjs.base';
|
import { Promise, TPromise } from 'vs/base/common/winjs.base';
|
||||||
@@ -86,7 +87,7 @@ export class JobStepsViewRenderer implements tree.IRenderer {
|
|||||||
private _statusIcon: HTMLElement;
|
private _statusIcon: HTMLElement;
|
||||||
|
|
||||||
public getHeight(tree: tree.ITree, element: JobStepsViewRow): number {
|
public getHeight(tree: tree.ITree, element: JobStepsViewRow): number {
|
||||||
return 22 * Math.ceil(element.message.length/JobManagementUtilities.jobMessageLength);
|
return 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTemplateId(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): string {
|
public getTemplateId(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): string {
|
||||||
@@ -118,6 +119,7 @@ export class JobStepsViewRenderer implements tree.IRenderer {
|
|||||||
let stepMessageCol: HTMLElement = DOM.$('div');
|
let stepMessageCol: HTMLElement = DOM.$('div');
|
||||||
stepMessageCol.className = 'tree-message-col';
|
stepMessageCol.className = 'tree-message-col';
|
||||||
stepMessageCol.innerText = element.message;
|
stepMessageCol.innerText = element.message;
|
||||||
|
$(templateData.label).empty();
|
||||||
templateData.label.appendChild(stepIdCol);
|
templateData.label.appendChild(stepIdCol);
|
||||||
templateData.label.appendChild(stepNameCol);
|
templateData.label.appendChild(stepNameCol);
|
||||||
templateData.label.appendChild(stepMessageCol);
|
templateData.label.appendChild(stepMessageCol);
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ import { IDashboardService } from 'sql/services/dashboard/common/dashboardServic
|
|||||||
import { escape } from 'sql/base/common/strings';
|
import { escape } from 'sql/base/common/strings';
|
||||||
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||||
import { tableBackground, cellBackground, cellBorderColor } from 'sql/common/theme/colors';
|
import { tableBackground, cellBackground, cellBorderColor } from 'sql/common/theme/colors';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||||
|
|
||||||
export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
|
export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
|
||||||
export const ROW_HEIGHT: number = 45;
|
export const ROW_HEIGHT: number = 45;
|
||||||
@@ -106,7 +108,8 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
|
|||||||
@Inject(IInstantiationService) instantiationService: IInstantiationService,
|
@Inject(IInstantiationService) instantiationService: IInstantiationService,
|
||||||
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
|
||||||
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
@Inject(IKeybindingService) keybindingService: IKeybindingService,
|
||||||
@Inject(IDashboardService) _dashboardService: IDashboardService
|
@Inject(IDashboardService) _dashboardService: IDashboardService,
|
||||||
|
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
|
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
|
||||||
this._didTabChange = false;
|
this._didTabChange = false;
|
||||||
@@ -127,6 +130,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
|
|||||||
this._visibilityElement = this._gridEl;
|
this._visibilityElement = this._gridEl;
|
||||||
this._parentComponent = this._agentViewComponent;
|
this._parentComponent = this._agentViewComponent;
|
||||||
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e)));
|
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e)));
|
||||||
|
this._telemetryService.publicLog(TelemetryKeys.JobsView);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
@@ -933,19 +937,19 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
|
|||||||
// add steps
|
// add steps
|
||||||
if (this.jobSteps && this.jobSteps[jobId]) {
|
if (this.jobSteps && this.jobSteps[jobId]) {
|
||||||
let steps = this.jobSteps[jobId];
|
let steps = this.jobSteps[jobId];
|
||||||
job[0].JobSteps = steps;
|
job[0].jobSteps = steps;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add schedules
|
// add schedules
|
||||||
if (this.jobSchedules && this.jobSchedules[jobId]) {
|
if (this.jobSchedules && this.jobSchedules[jobId]) {
|
||||||
let schedules = this.jobSchedules[jobId];
|
let schedules = this.jobSchedules[jobId];
|
||||||
job[0].JobSchedules = schedules;
|
job[0].jobSchedules = schedules;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add alerts
|
// add alerts
|
||||||
if (this.jobAlerts && this.jobAlerts[jobId]) {
|
if (this.jobAlerts && this.jobAlerts[jobId]) {
|
||||||
let alerts = this.jobAlerts[jobId];
|
let alerts = this.jobAlerts[jobId];
|
||||||
job[0].Alerts = alerts;
|
job[0].alerts = alerts;
|
||||||
}
|
}
|
||||||
return job && job.length > 0 ? job[0] : undefined;
|
return job && job.length > 0 ? job[0] : undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,20 @@ export class QueryTextEditor extends BaseTextEditor {
|
|||||||
if (!this._config) {
|
if (!this._config) {
|
||||||
this._config = new Configuration(undefined, editorWidget.getDomNode());
|
this._config = new Configuration(undefined, editorWidget.getDomNode());
|
||||||
}
|
}
|
||||||
let editorHeightUsingLines = this._config.editor.lineHeight * editorWidget.getModel().getLineCount();
|
let editorWidgetModel = editorWidget.getModel();
|
||||||
|
let lineCount = editorWidgetModel.getLineCount();
|
||||||
|
// Need to also keep track of lines that wrap; if we just keep into account line count, then the editor's height would not be
|
||||||
|
// tall enough and we would need to show a scrollbar. Unfortunately, it looks like there isn't any metadata saved in a ICodeEditor
|
||||||
|
// around max column length for an editor (which we could leverage to see if we need to loop through every line to determine
|
||||||
|
// number of lines that wrap). Finally, viewportColumn is calculated on editor resizing automatically; we can use it to ensure
|
||||||
|
// that the viewportColumn will always be greater than any character's column in an editor.
|
||||||
|
let numberWrappedLines = 0;
|
||||||
|
for (let line = 1; line <= lineCount; line++) {
|
||||||
|
if (editorWidgetModel.getLineMaxColumn(line) >= this._config.editor.layoutInfo.viewportColumn - 1) {
|
||||||
|
numberWrappedLines += Math.ceil(editorWidgetModel.getLineMaxColumn(line) / this._config.editor.layoutInfo.viewportColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let editorHeightUsingLines = this._config.editor.lineHeight * (lineCount + numberWrappedLines);
|
||||||
let editorHeightUsingMinHeight = Math.max(editorHeightUsingLines, this._minHeight);
|
let editorHeightUsingMinHeight = Math.max(editorHeightUsingLines, this._minHeight);
|
||||||
this.setHeight(editorHeightUsingMinHeight);
|
this.setHeight(editorHeightUsingMinHeight);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,9 +82,7 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
|||||||
if (propName === 'activeCellId') {
|
if (propName === 'activeCellId') {
|
||||||
let changedProp = changes[propName];
|
let changedProp = changes[propName];
|
||||||
this._activeCellId = changedProp.currentValue;
|
this._activeCellId = changedProp.currentValue;
|
||||||
if (this._activeCellId) {
|
this.toggleEditMode(false);
|
||||||
this.toggleEditMode(false);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export class CellModel implements ICellModel {
|
|||||||
private _active: boolean;
|
private _active: boolean;
|
||||||
private _cellUri: URI;
|
private _cellUri: URI;
|
||||||
|
|
||||||
constructor(private factory: IModelFactory, cellData?: nb.ICell, private _options?: ICellModelOptions) {
|
constructor(private factory: IModelFactory, cellData?: nb.ICellContents, private _options?: ICellModelOptions) {
|
||||||
this.id = `${modelId++}`;
|
this.id = `${modelId++}`;
|
||||||
CellModel.CreateLanguageMappings();
|
CellModel.CreateLanguageMappings();
|
||||||
// Do nothing for now
|
// Do nothing for now
|
||||||
@@ -263,8 +263,8 @@ export class CellModel implements ICellModel {
|
|||||||
return transient['display_id'] as string;
|
return transient['display_id'] as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
public toJSON(): nb.ICell {
|
public toJSON(): nb.ICellContents {
|
||||||
let cellJson: Partial<nb.ICell> = {
|
let cellJson: Partial<nb.ICellContents> = {
|
||||||
cell_type: this._cellType,
|
cell_type: this._cellType,
|
||||||
source: this._source,
|
source: this._source,
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -275,10 +275,10 @@ export class CellModel implements ICellModel {
|
|||||||
cellJson.outputs = this._outputs;
|
cellJson.outputs = this._outputs;
|
||||||
cellJson.execution_count = 1; // TODO: keep track of actual execution count
|
cellJson.execution_count = 1; // TODO: keep track of actual execution count
|
||||||
}
|
}
|
||||||
return cellJson as nb.ICell;
|
return cellJson as nb.ICellContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
public fromJSON(cell: nb.ICell): void {
|
public fromJSON(cell: nb.ICellContents): void {
|
||||||
if (!cell) {
|
if (!cell) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { ClientSession } from './clientSession';
|
|||||||
|
|
||||||
export class ModelFactory implements IModelFactory {
|
export class ModelFactory implements IModelFactory {
|
||||||
|
|
||||||
public createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel {
|
public createCell(cell: nb.ICellContents, options: ICellModelOptions): ICellModel {
|
||||||
return new CellModel(this, cell, options);
|
return new CellModel(this, cell, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import { INotebookManager } from 'sql/services/notebook/notebookService';
|
|||||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||||
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
||||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||||
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
|
||||||
export interface IClientSessionOptions {
|
export interface IClientSessionOptions {
|
||||||
notebookUri: URI;
|
notebookUri: URI;
|
||||||
@@ -328,6 +329,14 @@ export interface INotebookModel {
|
|||||||
* Notifies the notebook of a change in the cell
|
* Notifies the notebook of a change in the cell
|
||||||
*/
|
*/
|
||||||
onCellChange(cell: ICellModel, change: NotebookChangeType): void;
|
onCellChange(cell: ICellModel, change: NotebookChangeType): void;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push edit operations, basically editing the model. This is the preferred way of
|
||||||
|
* editing the model. Long-term, this will ensure edit operations can be added to the undo stack
|
||||||
|
* @param edits The edit operations to perform
|
||||||
|
*/
|
||||||
|
pushEditOperations(edits: ISingleNotebookEditOperation[]): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICellModelOptions {
|
export interface ICellModelOptions {
|
||||||
@@ -348,7 +357,7 @@ export interface ICellModel {
|
|||||||
readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>;
|
readonly onOutputsChanged: Event<ReadonlyArray<nb.ICellOutput>>;
|
||||||
setFuture(future: FutureInternal): void;
|
setFuture(future: FutureInternal): void;
|
||||||
equals(cellModel: ICellModel): boolean;
|
equals(cellModel: ICellModel): boolean;
|
||||||
toJSON(): nb.ICell;
|
toJSON(): nb.ICellContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FutureInternal extends nb.IFuture {
|
export interface FutureInternal extends nb.IFuture {
|
||||||
@@ -357,7 +366,7 @@ export interface FutureInternal extends nb.IFuture {
|
|||||||
|
|
||||||
export interface IModelFactory {
|
export interface IModelFactory {
|
||||||
|
|
||||||
createCell(cell: nb.ICell, options: ICellModelOptions): ICellModel;
|
createCell(cell: nb.ICellContents, options: ICellModelOptions): ICellModel;
|
||||||
createClientSession(options: IClientSessionOptions): IClientSession;
|
createClientSession(options: IClientSessionOptions): IClientSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
|||||||
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
||||||
import { INotification, Severity } from 'vs/platform/notification/common/notification';
|
import { INotification, Severity } from 'vs/platform/notification/common/notification';
|
||||||
import { Schemas } from 'vs/base/common/network';
|
import { Schemas } from 'vs/base/common/network';
|
||||||
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Used to control whether a message in a dialog/wizard is displayed as an error,
|
* Used to control whether a message in a dialog/wizard is displayed as an error,
|
||||||
@@ -237,7 +238,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createCell(cellType: CellType): ICellModel {
|
private createCell(cellType: CellType): ICellModel {
|
||||||
let singleCell: nb.ICell = {
|
let singleCell: nb.ICellContents = {
|
||||||
cell_type: cellType,
|
cell_type: cellType,
|
||||||
source: '',
|
source: '',
|
||||||
metadata: {},
|
metadata: {},
|
||||||
@@ -263,6 +264,25 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pushEditOperations(edits: ISingleNotebookEditOperation[]): void {
|
||||||
|
if (this.inErrorState || !this._cells) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let edit of edits) {
|
||||||
|
let newCells: ICellModel[] = [];
|
||||||
|
if (edit.cell) {
|
||||||
|
// TODO: should we validate and complete required missing parameters?
|
||||||
|
let contents: nb.ICellContents = edit.cell as nb.ICellContents;
|
||||||
|
newCells.push(this.notebookOptions.factory.createCell(contents, { notebook: this, isTrusted: this._trustedMode }));
|
||||||
|
}
|
||||||
|
this._cells.splice(edit.range.start, edit.range.end - edit.range.start, ...newCells);
|
||||||
|
this._contentChangedEmitter.fire({
|
||||||
|
changeType: NotebookChangeType.CellsAdded
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public get activeCell(): ICellModel {
|
public get activeCell(): ICellModel {
|
||||||
return this._activeCell;
|
return this._activeCell;
|
||||||
}
|
}
|
||||||
@@ -281,9 +301,14 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
notebookManager: this.notebookManager,
|
notebookManager: this.notebookManager,
|
||||||
notificationService: this.notebookOptions.notificationService
|
notificationService: this.notebookOptions.notificationService
|
||||||
});
|
});
|
||||||
let id: string = this.connectionProfile ? this.connectionProfile.id : undefined;
|
let profile = this.connectionProfile as IConnectionProfile;
|
||||||
|
|
||||||
|
if (this.isValidKnoxConnection(profile)) {
|
||||||
|
this._hadoopConnection = new NotebookConnection(this.connectionProfile);
|
||||||
|
} else {
|
||||||
|
this._hadoopConnection = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
this._hadoopConnection = this.connectionProfile ? new NotebookConnection(this.connectionProfile) : undefined;
|
|
||||||
this._clientSession.initialize(this._hadoopConnection);
|
this._clientSession.initialize(this._hadoopConnection);
|
||||||
this._sessionLoadFinished = this._clientSession.ready.then(async () => {
|
this._sessionLoadFinished = this._clientSession.ready.then(async () => {
|
||||||
if (this._clientSession.isInErrorState) {
|
if (this._clientSession.isInErrorState) {
|
||||||
@@ -389,7 +414,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
|
|
||||||
// Get default language if saved in notebook file
|
// Get default language if saved in notebook file
|
||||||
// Otherwise, default to python
|
// Otherwise, default to python
|
||||||
private getDefaultLanguageInfo(notebook: nb.INotebook): nb.ILanguageInfo {
|
private getDefaultLanguageInfo(notebook: nb.INotebookContents): nb.ILanguageInfo {
|
||||||
return notebook!.metadata!.language_info || {
|
return notebook!.metadata!.language_info || {
|
||||||
name: 'python',
|
name: 'python',
|
||||||
version: '',
|
version: '',
|
||||||
@@ -398,7 +423,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get default kernel info if saved in notebook file
|
// Get default kernel info if saved in notebook file
|
||||||
private getSavedKernelInfo(notebook: nb.INotebook): nb.IKernelInfo {
|
private getSavedKernelInfo(notebook: nb.INotebookContents): nb.IKernelInfo {
|
||||||
return notebook!.metadata!.kernelspec;
|
return notebook!.metadata!.kernelspec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,8 +515,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
|||||||
/**
|
/**
|
||||||
* Serialize the model to JSON.
|
* Serialize the model to JSON.
|
||||||
*/
|
*/
|
||||||
toJSON(): nb.INotebook {
|
toJSON(): nb.INotebookContents {
|
||||||
let cells: nb.ICell[] = this.cells.map(c => c.toJSON());
|
let cells: nb.ICellContents[] = this.cells.map(c => c.toJSON());
|
||||||
let metadata = Object.create(null) as nb.INotebookMetadata;
|
let metadata = Object.create(null) as nb.INotebookMetadata;
|
||||||
// TODO update language and kernel when these change
|
// TODO update language and kernel when these change
|
||||||
metadata.kernelspec = this._savedKernelInfo;
|
metadata.kernelspec = this._savedKernelInfo;
|
||||||
|
|||||||
@@ -140,9 +140,16 @@ export class SparkMagicContexts {
|
|||||||
* @param savedKernelInfo kernel info loaded from
|
* @param savedKernelInfo kernel info loaded from
|
||||||
*/
|
*/
|
||||||
public static getDefaultKernel(specs: nb.IAllKernels, connectionInfo: IConnectionProfile, savedKernelInfo: nb.IKernelInfo, notificationService: INotificationService): nb.IKernelSpec {
|
public static getDefaultKernel(specs: nb.IAllKernels, connectionInfo: IConnectionProfile, savedKernelInfo: nb.IKernelInfo, notificationService: INotificationService): nb.IKernelSpec {
|
||||||
let defaultKernel = specs.kernels.find((kernel) => kernel.name === specs.defaultKernel);
|
let foundSavedKernelInSpecs;
|
||||||
|
let defaultKernel;
|
||||||
|
if (specs) {
|
||||||
|
defaultKernel = specs.kernels.find((kernel) => kernel.name === specs.defaultKernel);
|
||||||
|
if (savedKernelInfo) {
|
||||||
|
foundSavedKernelInSpecs = specs.kernels.find((kernel) => kernel.name === savedKernelInfo.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
let profile = connectionInfo as IConnectionProfile;
|
let profile = connectionInfo as IConnectionProfile;
|
||||||
if (specs && connectionInfo && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
|
if (foundSavedKernelInSpecs && specs && connectionInfo && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
|
||||||
// set default kernel to default spark kernel if profile exists
|
// set default kernel to default spark kernel if profile exists
|
||||||
// otherwise, set default to kernel info loaded from existing file
|
// otherwise, set default to kernel info loaded from existing file
|
||||||
defaultKernel = !savedKernelInfo ? specs.kernels.find((spec) => spec.name === notebookConstants.defaultSparkKernel) : savedKernelInfo;
|
defaultKernel = !savedKernelInfo ? specs.kernels.find((spec) => spec.name === notebookConstants.defaultSparkKernel) : savedKernelInfo;
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
||||||
<div #toolbar class="editor-toolbar actionbar-container" style="flex: 0 0 auto; display: flex; flex-flow: row; width: 100%; align-items: center; height: 36px">
|
<div #toolbar class="editor-toolbar actionbar-container" style="flex: 0 0 auto; display: flex; flex-flow: row; width: 100%; align-items: center; height: 36px">
|
||||||
</div>
|
</div>
|
||||||
<div class="scrollable" style="flex: 1 1 auto; position: relative">
|
<div class="scrollable" style="flex: 1 1 auto; position: relative" (click)="unselectActiveCell()">
|
||||||
<loading-spinner [loading]="isLoading"></loading-spinner>
|
<loading-spinner [loading]="isLoading"></loading-spinner>
|
||||||
<div class="notebook-cell" *ngFor="let cell of cells" (click)="selectCell(cell)" [class.active]="cell.active" (keydown)="onKeyDown($event)">
|
<div class="notebook-cell" *ngFor="let cell of cells" (click)="selectCell(cell, $event)" [class.active]="cell.active">
|
||||||
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
|
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
|
||||||
</code-cell-component>
|
</code-cell-component>
|
||||||
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
|
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import './notebookStyles';
|
|||||||
|
|
||||||
import { nb } from 'sqlops';
|
import { nb } from 'sqlops';
|
||||||
|
|
||||||
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild } from '@angular/core';
|
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnDestroy } from '@angular/core';
|
||||||
|
|
||||||
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||||
import * as themeColors from 'vs/workbench/common/theme';
|
import * as themeColors from 'vs/workbench/common/theme';
|
||||||
@@ -20,7 +20,7 @@ import { AngularDisposable } from 'sql/base/common/lifecycle';
|
|||||||
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
||||||
import { ICellModel, IModelFactory, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces';
|
import { ICellModel, IModelFactory, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
|
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
|
||||||
import { INotebookService, INotebookParams, INotebookManager } from 'sql/services/notebook/notebookService';
|
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor } from 'sql/services/notebook/notebookService';
|
||||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||||
import { NotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel';
|
import { NotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel';
|
||||||
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||||
@@ -40,6 +40,7 @@ import { fillInActions, LabeledMenuItemActionItem } from 'vs/platform/actions/br
|
|||||||
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
|
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
|
||||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
|
||||||
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
|
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ export const NOTEBOOK_SELECTOR: string = 'notebook-component';
|
|||||||
selector: NOTEBOOK_SELECTOR,
|
selector: NOTEBOOK_SELECTOR,
|
||||||
templateUrl: decodeURI(require.toUrl('./notebook.component.html'))
|
templateUrl: decodeURI(require.toUrl('./notebook.component.html'))
|
||||||
})
|
})
|
||||||
export class NotebookComponent extends AngularDisposable implements OnInit {
|
export class NotebookComponent extends AngularDisposable implements OnInit, OnDestroy, INotebookEditor {
|
||||||
@ViewChild('toolbar', { read: ElementRef }) private toolbar: ElementRef;
|
@ViewChild('toolbar', { read: ElementRef }) private toolbar: ElementRef;
|
||||||
private _model: NotebookModel;
|
private _model: NotebookModel;
|
||||||
private _isInErrorState: boolean = false;
|
private _isInErrorState: boolean = false;
|
||||||
@@ -73,7 +74,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
|||||||
@Inject(IEditorService) private editorService: IEditorService,
|
@Inject(IEditorService) private editorService: IEditorService,
|
||||||
@Inject(INotificationService) private notificationService: INotificationService,
|
@Inject(INotificationService) private notificationService: INotificationService,
|
||||||
@Inject(INotebookService) private notebookService: INotebookService,
|
@Inject(INotebookService) private notebookService: INotebookService,
|
||||||
@Inject(IBootstrapParams) private notebookParams: INotebookParams,
|
@Inject(IBootstrapParams) private _notebookParams: INotebookParams,
|
||||||
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
|
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
|
||||||
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
|
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
|
||||||
@Inject(IContextViewService) private contextViewService: IContextViewService,
|
@Inject(IContextViewService) private contextViewService: IContextViewService,
|
||||||
@@ -108,10 +109,17 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
|||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||||
this.updateTheme(this.themeService.getColorTheme());
|
this.updateTheme(this.themeService.getColorTheme());
|
||||||
|
this.notebookService.addNotebookEditor(this);
|
||||||
this.initActionBar();
|
this.initActionBar();
|
||||||
this.doLoad();
|
this.doLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.notebookService) {
|
||||||
|
this.notebookService.removeNotebookEditor(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public get model(): NotebookModel {
|
public get model(): NotebookModel {
|
||||||
return this._model;
|
return this._model;
|
||||||
}
|
}
|
||||||
@@ -133,7 +141,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
|||||||
toolbarEl.style.borderBottomColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
toolbarEl.style.borderBottomColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectCell(cell: ICellModel) {
|
public selectCell(cell: ICellModel, event?: Event) {
|
||||||
|
if (event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
if (cell !== this._activeCell) {
|
if (cell !== this._activeCell) {
|
||||||
if (this._activeCell) {
|
if (this._activeCell) {
|
||||||
this._activeCell.active = false;
|
this._activeCell.active = false;
|
||||||
@@ -146,6 +157,16 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public unselectActiveCell() {
|
||||||
|
if (this._activeCell) {
|
||||||
|
this._activeCell.active = false;
|
||||||
|
}
|
||||||
|
this._activeCell = null;
|
||||||
|
this._model.activeCell = null;
|
||||||
|
this._activeCellId = null;
|
||||||
|
this._changeRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
// Add cell based on cell type
|
// Add cell based on cell type
|
||||||
public addCell(cellType: CellType)
|
public addCell(cellType: CellType)
|
||||||
{
|
{
|
||||||
@@ -201,16 +222,16 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadModel(): Promise<void> {
|
private async loadModel(): Promise<void> {
|
||||||
this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this.notebookParams.providerId, this.notebookParams.notebookUri);
|
this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this._notebookParams.providerId, this._notebookParams.notebookUri);
|
||||||
let model = new NotebookModel({
|
let model = new NotebookModel({
|
||||||
factory: this.modelFactory,
|
factory: this.modelFactory,
|
||||||
notebookUri: this.notebookParams.notebookUri,
|
notebookUri: this._notebookParams.notebookUri,
|
||||||
connectionService: this.connectionManagementService,
|
connectionService: this.connectionManagementService,
|
||||||
notificationService: this.notificationService,
|
notificationService: this.notificationService,
|
||||||
notebookManager: this.notebookManager
|
notebookManager: this.notebookManager
|
||||||
}, false, this.profile);
|
}, false, this.profile);
|
||||||
model.onError((errInfo: INotification) => this.handleModelError(errInfo));
|
model.onError((errInfo: INotification) => this.handleModelError(errInfo));
|
||||||
await model.requestModelLoad(this.notebookParams.isTrusted);
|
await model.requestModelLoad(this._notebookParams.isTrusted);
|
||||||
model.contentChanged((change) => this.handleContentChanged(change));
|
model.contentChanged((change) => this.handleContentChanged(change));
|
||||||
this._model = model;
|
this._model = model;
|
||||||
this.updateToolbarComponents(this._model.trustedMode);
|
this.updateToolbarComponents(this._model.trustedMode);
|
||||||
@@ -231,10 +252,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private get modelFactory(): IModelFactory {
|
private get modelFactory(): IModelFactory {
|
||||||
if (!this.notebookParams.modelFactory) {
|
if (!this._notebookParams.modelFactory) {
|
||||||
this.notebookParams.modelFactory = new ModelFactory();
|
this._notebookParams.modelFactory = new ModelFactory();
|
||||||
}
|
}
|
||||||
return this.notebookParams.modelFactory;
|
return this._notebookParams.modelFactory;
|
||||||
}
|
}
|
||||||
private handleModelError(notification: INotification): void {
|
private handleModelError(notification: INotification): void {
|
||||||
this.notificationService.notify(notification);
|
this.notificationService.notify(notification);
|
||||||
@@ -314,6 +335,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
|||||||
public async save(): Promise<boolean> {
|
public async save(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
let saved = await this._model.saveModel();
|
let saved = await this._model.saveModel();
|
||||||
|
if (saved) {
|
||||||
|
this.setDirty(false);
|
||||||
|
}
|
||||||
return saved;
|
return saved;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.notificationService.error(localize('saveFailed', 'Failed to save notebook: {0}', notebookUtils.getErrorMessage(err)));
|
this.notificationService.error(localize('saveFailed', 'Failed to save notebook: {0}', notebookUtils.getErrorMessage(err)));
|
||||||
@@ -322,10 +346,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private setDirty(isDirty: boolean): void {
|
private setDirty(isDirty: boolean): void {
|
||||||
// TODO reenable handling of isDirty
|
if(this._notebookParams.input){
|
||||||
// if (this.editor) {
|
this._notebookParams.input.setDirty(isDirty);
|
||||||
// this.editor.isDirty = isDirty;
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private actionItemProvider(action: Action): IActionItem {
|
private actionItemProvider(action: Action): IActionItem {
|
||||||
@@ -337,4 +360,32 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get notebookParams(): INotebookParams {
|
||||||
|
return this._notebookParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): string {
|
||||||
|
return this._notebookParams.notebookUri.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
isActive(): boolean {
|
||||||
|
return this.editorService.activeEditor === this.notebookParams.input;
|
||||||
|
}
|
||||||
|
|
||||||
|
isVisible(): boolean {
|
||||||
|
let notebookEditor = this.notebookParams.input;
|
||||||
|
return this.editorService.visibleEditors.some(e => e === notebookEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirty(): boolean {
|
||||||
|
return this.notebookParams.input.isDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
executeEdits(edits: ISingleNotebookEditOperation[]): boolean {
|
||||||
|
if (!edits || edits.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
this._model.pushEditOperations(edits);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,50 +4,10 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||||
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
|
||||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
|
||||||
import { Action } from 'vs/base/common/actions';
|
|
||||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
|
||||||
import { Schemas } from 'vs/base/common/network';
|
|
||||||
import URI from 'vs/base/common/uri';
|
|
||||||
import { localize } from 'vs/nls';
|
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
|
||||||
|
|
||||||
import { NotebookInput, NotebookInputModel, notebooksEnabledCondition } from 'sql/parts/notebook/notebookInput';
|
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
|
||||||
import { NotebookEditor } from 'sql/parts/notebook/notebookEditor';
|
import { NotebookEditor } from 'sql/parts/notebook/notebookEditor';
|
||||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
|
||||||
|
|
||||||
|
|
||||||
let counter = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* todo: Will remove this code.
|
|
||||||
* This is the entry point to open the new Notebook
|
|
||||||
*/
|
|
||||||
export class NewNotebookAction extends Action {
|
|
||||||
|
|
||||||
public static ID = 'workbench.action.newnotebook';
|
|
||||||
public static LABEL = localize('workbench.action.newnotebook.description', 'New Notebook');
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
id: string,
|
|
||||||
label: string,
|
|
||||||
@IEditorService private _editorService: IEditorService,
|
|
||||||
@IInstantiationService private _instantiationService: IInstantiationService
|
|
||||||
) {
|
|
||||||
super(id, label);
|
|
||||||
}
|
|
||||||
|
|
||||||
public run(): TPromise<void> {
|
|
||||||
let title = `Untitled-${counter++}`;
|
|
||||||
let untitledUri = URI.from({ scheme: Schemas.untitled, path: title });
|
|
||||||
let model = new NotebookInputModel(untitledUri, undefined, false, undefined);
|
|
||||||
let input = this._instantiationService.createInstance(NotebookInput, title, model);
|
|
||||||
return this._editorService.openEditor(input, { pinned: true }).then(() => undefined);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Model View editor registration
|
// Model View editor registration
|
||||||
const viewModelEditorDescriptor = new EditorDescriptor(
|
const viewModelEditorDescriptor = new EditorDescriptor(
|
||||||
@@ -58,31 +18,3 @@ const viewModelEditorDescriptor = new EditorDescriptor(
|
|||||||
|
|
||||||
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
|
||||||
.registerEditor(viewModelEditorDescriptor, [new SyncDescriptor(NotebookInput)]);
|
.registerEditor(viewModelEditorDescriptor, [new SyncDescriptor(NotebookInput)]);
|
||||||
|
|
||||||
// Feature flag for built-in Notebooks. Will be removed in the future.
|
|
||||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigExtensions.Configuration);
|
|
||||||
configurationRegistry.registerConfiguration({
|
|
||||||
'id': 'notebook',
|
|
||||||
'title': 'Notebook',
|
|
||||||
'type': 'object',
|
|
||||||
'properties': {
|
|
||||||
'notebook.enabled': {
|
|
||||||
'type': 'boolean',
|
|
||||||
'default': false,
|
|
||||||
'description': localize('notebook.enabledDescription', 'Enable viewing notebook files using built-in notebook editor.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// this is the entry point to open the new Notebook
|
|
||||||
CommandsRegistry.registerCommand(NewNotebookAction.ID, serviceAccessor => {
|
|
||||||
serviceAccessor.get(IInstantiationService).createInstance(NewNotebookAction, NewNotebookAction.ID, NewNotebookAction.LABEL).run();
|
|
||||||
});
|
|
||||||
|
|
||||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
|
||||||
command: {
|
|
||||||
id: NewNotebookAction.ID,
|
|
||||||
title:NewNotebookAction.LABEL,
|
|
||||||
},
|
|
||||||
when: notebooksEnabledCondition
|
|
||||||
});
|
|
||||||
@@ -36,6 +36,10 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notebookEditor .monaco-select-box {
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
.notebookEditor .notebook-button.icon-add{
|
.notebookEditor .notebook-button.icon-add{
|
||||||
background-image: url("./media/light/add.svg");
|
background-image: url("./media/light/add.svg");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { NotebookComponent } from 'sql/parts/notebook/notebook.component';
|
|||||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||||
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
|
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
|
||||||
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
|
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
|
||||||
|
import { noKernel } from 'sql/services/notebook/sessionManager';
|
||||||
|
|
||||||
const msgLoading = localize('loading', 'Loading kernels...');
|
const msgLoading = localize('loading', 'Loading kernels...');
|
||||||
const kernelLabel: string = localize('Kernel', 'Kernel: ');
|
const kernelLabel: string = localize('Kernel', 'Kernel: ');
|
||||||
@@ -238,7 +239,7 @@ export class AttachToDropdown extends SelectBox {
|
|||||||
|
|
||||||
// Load "Attach To" dropdown with the values corresponding to Kernel dropdown
|
// Load "Attach To" dropdown with the values corresponding to Kernel dropdown
|
||||||
public async loadAttachToDropdown(model: INotebookModel, currentKernel: string): Promise<void> {
|
public async loadAttachToDropdown(model: INotebookModel, currentKernel: string): Promise<void> {
|
||||||
if (currentKernel === notebookConstants.python3) {
|
if (currentKernel === notebookConstants.python3 || currentKernel === noKernel) {
|
||||||
this.setOptions([msgLocalHost]);
|
this.setOptions([msgLocalHost]);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -314,6 +315,7 @@ export class AttachToDropdown extends SelectBox {
|
|||||||
attachToConnections = attachToConnections.filter(val => val !== msgSelectConnection);
|
attachToConnections = attachToConnections.filter(val => val !== msgSelectConnection);
|
||||||
|
|
||||||
let index = attachToConnections.findIndex((connection => connection === connectedServer));
|
let index = attachToConnections.findIndex((connection => connection === connectedServer));
|
||||||
|
this.setOptions([]);
|
||||||
this.setOptions(attachToConnections);
|
this.setOptions(attachToConnections);
|
||||||
if (!index || index < 0 || index >= attachToConnections.length) {
|
if (!index || index < 0 || index >= attachToConnections.length) {
|
||||||
index = 0;
|
index = 0;
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export class NotebookEditor extends BaseEditor {
|
|||||||
input.hasBootstrapped = true;
|
input.hasBootstrapped = true;
|
||||||
let params: INotebookParams = {
|
let params: INotebookParams = {
|
||||||
notebookUri: input.notebookUri,
|
notebookUri: input.notebookUri,
|
||||||
|
input: input,
|
||||||
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER,
|
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER,
|
||||||
isTrusted: input.isTrusted
|
isTrusted: input.isTrusted
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/edi
|
|||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
import URI from 'vs/base/common/uri';
|
import URI from 'vs/base/common/uri';
|
||||||
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import * as resources from 'vs/base/common/resources';
|
||||||
|
|
||||||
import { INotebookService } from 'sql/services/notebook/notebookService';
|
import { INotebookService } from 'sql/services/notebook/notebookService';
|
||||||
|
|
||||||
@@ -88,15 +89,6 @@ export class NotebookInput extends EditorInput {
|
|||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire());
|
this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire());
|
||||||
this.onDispose(() => {
|
|
||||||
if (this.notebookService) {
|
|
||||||
this.notebookService.handleNotebookClosed(this.notebookUri);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public get title(): string {
|
|
||||||
return this._title;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get notebookUri(): URI {
|
public get notebookUri(): URI {
|
||||||
@@ -116,6 +108,10 @@ export class NotebookInput extends EditorInput {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getName(): string {
|
public getName(): string {
|
||||||
|
if (!this._title) {
|
||||||
|
this._title = resources.basenameOrAuthority(this._model.notebookUri);
|
||||||
|
}
|
||||||
|
|
||||||
return this._title;
|
return this._title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,4 +169,12 @@ export class NotebookInput extends EditorInput {
|
|||||||
save(): TPromise<boolean> {
|
save(): TPromise<boolean> {
|
||||||
return this._model.save();
|
return this._model.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets active editor with dirty value.
|
||||||
|
* @param isDirty boolean value to set editor dirty
|
||||||
|
*/
|
||||||
|
setDirty(isDirty: boolean): void {
|
||||||
|
this._model.setDirty(isDirty);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,8 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
|
|||||||
collector.addRule(`
|
collector.addRule(`
|
||||||
.notebookEditor .notebook-cell.active {
|
.notebookEditor .notebook-cell.active {
|
||||||
border-color: ${activeBorder};
|
border-color: ${activeBorder};
|
||||||
border-width: 2px;
|
border-width: 1px;
|
||||||
|
box-shadow: 0px 4px 6px 0px rgba(0,0,0,0.14);
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,15 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import * as path from 'path';
|
||||||
import { nb } from 'sqlops';
|
import { nb } from 'sqlops';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
import * as pfs from 'vs/base/node/pfs';
|
import * as pfs from 'vs/base/node/pfs';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { IOutputChannel } from 'vs/workbench/parts/output/common/output';
|
import { IOutputChannel } from 'vs/workbench/parts/output/common/output';
|
||||||
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
|
import { INotebookProviderRegistry, Extensions } from 'sql/services/notebook/notebookRegistry';
|
||||||
|
import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE } from 'sql/services/notebook/notebookService';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,3 +40,23 @@ export async function mkDir(dirPath: string, outputChannel?: IOutputChannel): Pr
|
|||||||
await pfs.mkdirp(dirPath);
|
await pfs.mkdirp(dirPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getProviderForFileName(fileName: string): string {
|
||||||
|
let fileExt = path.extname(fileName);
|
||||||
|
let provider: string;
|
||||||
|
let notebookRegistry = Registry.as<INotebookProviderRegistry>(Extensions.NotebookProviderContribution);
|
||||||
|
// First try to get provider for actual file type
|
||||||
|
if (fileExt && fileExt.startsWith('.')) {
|
||||||
|
fileExt = fileExt.slice(1,fileExt.length);
|
||||||
|
provider = notebookRegistry.getProviderForFileType(fileExt);
|
||||||
|
}
|
||||||
|
// Fallback to provider for default file type (assume this is a global handler)
|
||||||
|
if (!provider) {
|
||||||
|
provider = notebookRegistry.getProviderForFileType(DEFAULT_NOTEBOOK_FILETYPE);
|
||||||
|
}
|
||||||
|
// Finally if all else fails, use the built-in handler
|
||||||
|
if (!provider) {
|
||||||
|
provider = DEFAULT_NOTEBOOK_PROVIDER;
|
||||||
|
}
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiati
|
|||||||
import * as nls from 'vs/nls';
|
import * as nls from 'vs/nls';
|
||||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||||
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||||
import { IConnectionManagementService, IConnectionDialogService} from 'sql/parts/connection/common/connectionManagement';
|
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
|
||||||
import { IObjectExplorerService } from '../../objectExplorer/common/objectExplorerService';
|
import { IObjectExplorerService } from '../../objectExplorer/common/objectExplorerService';
|
||||||
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
@@ -55,10 +55,10 @@ CommandsRegistry.registerCommand({
|
|||||||
|
|
||||||
let promise;
|
let promise;
|
||||||
if (connectionProfile) {
|
if (connectionProfile) {
|
||||||
promise = connectionService.connectIfNotConnected(connectionProfile);
|
promise = connectionService.connectIfNotConnected(connectionProfile, 'connection', true);
|
||||||
} else {
|
} else {
|
||||||
// if still no luck, we will open the Connection dialog and let user connect to a server
|
// if still no luck, we will open the Connection dialog and let user connect to a server
|
||||||
promise = connectionDialogService.openDialogAndWait(connectionService, { connectionType: 1, providers: [mssqlProviderName] }).then((profile) => {
|
promise = connectionDialogService.openDialogAndWait(connectionService, { connectionType: 0, showDashboard: false, providers: [mssqlProviderName] }).then((profile) => {
|
||||||
connectionProfile = profile as ConnectionProfile;
|
connectionProfile = profile as ConnectionProfile;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -308,8 +308,6 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas
|
|||||||
// ----- Public
|
// ----- Public
|
||||||
|
|
||||||
public focusFindInput(): void {
|
public focusFindInput(): void {
|
||||||
this._findInput.select();
|
|
||||||
// Edge browser requires focus() in addition to select()
|
|
||||||
this._findInput.focus();
|
this._findInput.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
|
|||||||
if (p) {
|
if (p) {
|
||||||
this._profilerTable.setActiveCell(p.row, p.col);
|
this._profilerTable.setActiveCell(p.row, p.col);
|
||||||
this._updateFinderMatchState();
|
this._updateFinderMatchState();
|
||||||
|
this._finder.focusFindInput();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -557,6 +557,7 @@ export class ProfilerEditor extends BaseEditor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public focus() {
|
public focus() {
|
||||||
|
this._profilerEditorContextKey.set(true);
|
||||||
super.focus();
|
super.focus();
|
||||||
let savedViewState = this._savedTableViewStates.get(this.input);
|
let savedViewState = this._savedTableViewStates.get(this.input);
|
||||||
if (savedViewState) {
|
if (savedViewState) {
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
|
|||||||
let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => {
|
let searchFn = (val: { [x: string]: string }, exp: string): Array<number> => {
|
||||||
let ret = new Array<number>();
|
let ret = new Array<number>();
|
||||||
for (let i = 0; i < this._columns.length; i++) {
|
for (let i = 0; i < this._columns.length; i++) {
|
||||||
if (val[this._columns[i]].includes(exp)) {
|
let colVal = val[this._columns[i]];
|
||||||
|
if (colVal && colVal.toLocaleLowerCase().includes(exp.toLocaleLowerCase())) {
|
||||||
ret.push(i);
|
ret.push(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ export class QueryResultsView extends Disposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
|
dispose(this.runnerDisposables);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ export class CustomDialogService {
|
|||||||
|
|
||||||
constructor( @IInstantiationService private _instantiationService: IInstantiationService) { }
|
constructor( @IInstantiationService private _instantiationService: IInstantiationService) { }
|
||||||
|
|
||||||
public showDialog(dialog: Dialog, options?: IModalOptions): void {
|
public showDialog(dialog: Dialog, dialogName?: string, options?: IModalOptions): void {
|
||||||
let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, 'CustomDialog', options || defaultOptions);
|
let name = dialogName ? dialogName : 'CustomDialog';
|
||||||
|
let dialogModal = this._instantiationService.createInstance(DialogModal, dialog, name, options || defaultOptions);
|
||||||
this._dialogModals.set(dialog, dialogModal);
|
this._dialogModals.set(dialog, dialogModal);
|
||||||
dialogModal.render();
|
dialogModal.render();
|
||||||
dialogModal.open();
|
dialogModal.open();
|
||||||
|
|||||||
@@ -12,10 +12,9 @@ import * as pfs from 'vs/base/node/pfs';
|
|||||||
import URI from 'vs/base/common/uri';
|
import URI from 'vs/base/common/uri';
|
||||||
|
|
||||||
import ContentManager = nb.ContentManager;
|
import ContentManager = nb.ContentManager;
|
||||||
import INotebook = nb.INotebook;
|
|
||||||
|
|
||||||
export class LocalContentManager implements ContentManager {
|
export class LocalContentManager implements ContentManager {
|
||||||
public async getNotebookContents(notebookUri: URI): Promise<INotebook> {
|
public async getNotebookContents(notebookUri: URI): Promise<nb.INotebookContents> {
|
||||||
if (!notebookUri) {
|
if (!notebookUri) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -23,10 +22,10 @@ export class LocalContentManager implements ContentManager {
|
|||||||
let path = notebookUri.fsPath;
|
let path = notebookUri.fsPath;
|
||||||
// Note: intentionally letting caller handle exceptions
|
// Note: intentionally letting caller handle exceptions
|
||||||
let notebookFileBuffer = await pfs.readFile(path);
|
let notebookFileBuffer = await pfs.readFile(path);
|
||||||
return <INotebook>json.parse(notebookFileBuffer.toString());
|
return <nb.INotebookContents>json.parse(notebookFileBuffer.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
public async save(notebookUri: URI, notebook: INotebook): Promise<INotebook> {
|
public async save(notebookUri: URI, notebook: nb.INotebookContents): Promise<nb.INotebookContents> {
|
||||||
// Convert to JSON with pretty-print functionality
|
// Convert to JSON with pretty-print functionality
|
||||||
let contents = JSON.stringify(notebook, undefined, ' ');
|
let contents = JSON.stringify(notebook, undefined, ' ');
|
||||||
let path = notebookUri.fsPath;
|
let path = notebookUri.fsPath;
|
||||||
|
|||||||
@@ -6,21 +6,29 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
|
|
||||||
|
import { Event } from 'vs/base/common/event';
|
||||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import URI from 'vs/base/common/uri';
|
import URI from 'vs/base/common/uri';
|
||||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||||
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
||||||
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||||
|
import { NotebookInput } from 'sql/parts/notebook/notebookInput';
|
||||||
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
|
||||||
export const SERVICE_ID = 'notebookService';
|
export const SERVICE_ID = 'notebookService';
|
||||||
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
|
export const INotebookService = createDecorator<INotebookService>(SERVICE_ID);
|
||||||
|
|
||||||
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
|
export const DEFAULT_NOTEBOOK_PROVIDER = 'builtin';
|
||||||
|
export const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
|
||||||
|
|
||||||
export interface INotebookService {
|
export interface INotebookService {
|
||||||
_serviceBrand: any;
|
_serviceBrand: any;
|
||||||
|
|
||||||
|
onNotebookEditorAdd: Event<INotebookEditor>;
|
||||||
|
onNotebookEditorRemove: Event<INotebookEditor>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a metadata provider
|
* Register a metadata provider
|
||||||
*/
|
*/
|
||||||
@@ -40,7 +48,11 @@ export interface INotebookService {
|
|||||||
*/
|
*/
|
||||||
getOrCreateNotebookManager(providerId: string, uri: URI): Thenable<INotebookManager>;
|
getOrCreateNotebookManager(providerId: string, uri: URI): Thenable<INotebookManager>;
|
||||||
|
|
||||||
handleNotebookClosed(uri: URI): void;
|
addNotebookEditor(editor: INotebookEditor): void;
|
||||||
|
|
||||||
|
removeNotebookEditor(editor: INotebookEditor): void;
|
||||||
|
|
||||||
|
listNotebookEditors(): INotebookEditor[];
|
||||||
|
|
||||||
shutdown(): void;
|
shutdown(): void;
|
||||||
|
|
||||||
@@ -62,8 +74,19 @@ export interface INotebookManager {
|
|||||||
|
|
||||||
export interface INotebookParams extends IBootstrapParams {
|
export interface INotebookParams extends IBootstrapParams {
|
||||||
notebookUri: URI;
|
notebookUri: URI;
|
||||||
|
input: NotebookInput;
|
||||||
providerId: string;
|
providerId: string;
|
||||||
isTrusted: boolean;
|
isTrusted: boolean;
|
||||||
profile?: IConnectionProfile;
|
profile?: IConnectionProfile;
|
||||||
modelFactory?: ModelFactory;
|
modelFactory?: ModelFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface INotebookEditor {
|
||||||
|
readonly notebookParams: INotebookParams;
|
||||||
|
readonly id: string;
|
||||||
|
isDirty(): boolean;
|
||||||
|
isActive(): boolean;
|
||||||
|
isVisible(): boolean;
|
||||||
|
save(): Promise<boolean>;
|
||||||
|
executeEdits(edits: ISingleNotebookEditOperation[]): boolean;
|
||||||
|
}
|
||||||
@@ -10,20 +10,26 @@ import { localize } from 'vs/nls';
|
|||||||
import URI from 'vs/base/common/uri';
|
import URI from 'vs/base/common/uri';
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
|
|
||||||
import { INotebookService, INotebookManager, INotebookProvider, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
import {
|
||||||
|
INotebookService, INotebookManager, INotebookProvider, DEFAULT_NOTEBOOK_PROVIDER,
|
||||||
|
DEFAULT_NOTEBOOK_FILETYPE, INotebookEditor
|
||||||
|
} from 'sql/services/notebook/notebookService';
|
||||||
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
import { RenderMimeRegistry } from 'sql/parts/notebook/outputs/registry';
|
||||||
import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories';
|
import { standardRendererFactories } from 'sql/parts/notebook/outputs/factories';
|
||||||
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
|
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
|
||||||
import { SessionManager } from 'sql/services/notebook/sessionManager';
|
import { SessionManager } from 'sql/services/notebook/sessionManager';
|
||||||
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
|
import { Extensions, INotebookProviderRegistry } from 'sql/services/notebook/notebookRegistry';
|
||||||
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
|
||||||
const DEFAULT_NOTEBOOK_FILETYPE = 'IPYNB';
|
|
||||||
|
|
||||||
export class NotebookService implements INotebookService {
|
export class NotebookService implements INotebookService {
|
||||||
_serviceBrand: any;
|
_serviceBrand: any;
|
||||||
private _mimeRegistry: RenderMimeRegistry;
|
private _mimeRegistry: RenderMimeRegistry;
|
||||||
private _providers: Map<string, INotebookProvider> = new Map();
|
private _providers: Map<string, INotebookProvider> = new Map();
|
||||||
private _managers: Map<string, INotebookManager> = new Map();
|
private _managers: Map<string, INotebookManager> = new Map();
|
||||||
|
private _onNotebookEditorAdd = new Emitter<INotebookEditor>();
|
||||||
|
private _onNotebookEditorRemove = new Emitter<INotebookEditor>();
|
||||||
|
private _editors = new Map<string, INotebookEditor>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.registerDefaultProvider();
|
this.registerDefaultProvider();
|
||||||
@@ -71,8 +77,34 @@ export class NotebookService implements INotebookService {
|
|||||||
return manager;
|
return manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleNotebookClosed(notebookUri: URI): void {
|
get onNotebookEditorAdd(): Event<INotebookEditor> {
|
||||||
|
return this._onNotebookEditorAdd.event;
|
||||||
|
}
|
||||||
|
get onNotebookEditorRemove(): Event<INotebookEditor> {
|
||||||
|
return this._onNotebookEditorRemove.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
addNotebookEditor(editor: INotebookEditor): void {
|
||||||
|
this._editors.set(editor.id, editor);
|
||||||
|
this._onNotebookEditorAdd.fire(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeNotebookEditor(editor: INotebookEditor): void {
|
||||||
|
if (this._editors.delete(editor.id)) {
|
||||||
|
this._onNotebookEditorRemove.fire(editor);
|
||||||
|
}
|
||||||
// Remove the manager from the tracked list, and let the notebook provider know that it should update its mappings
|
// Remove the manager from the tracked list, and let the notebook provider know that it should update its mappings
|
||||||
|
this.sendNotebookCloseToProvider(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
listNotebookEditors(): INotebookEditor[] {
|
||||||
|
let editors = [];
|
||||||
|
this._editors.forEach(e => editors.push(e));
|
||||||
|
return editors;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendNotebookCloseToProvider(editor: INotebookEditor) {
|
||||||
|
let notebookUri = editor.notebookParams.notebookUri;
|
||||||
let uriString = notebookUri.toString();
|
let uriString = notebookUri.toString();
|
||||||
let manager = this._managers.get(uriString);
|
let manager = this._managers.get(uriString);
|
||||||
if (manager) {
|
if (manager) {
|
||||||
@@ -82,15 +114,20 @@ export class NotebookService implements INotebookService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
||||||
private doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Thenable<T> {
|
private doWithProvider<T>(providerId: string, op: (provider: INotebookProvider) => Thenable<T>): Thenable<T> {
|
||||||
// Make sure the provider exists before attempting to retrieve accounts
|
// Make sure the provider exists before attempting to retrieve accounts
|
||||||
let provider = this._providers.get(providerId);
|
let provider: INotebookProvider;
|
||||||
|
if (this._providers.has(providerId)) {
|
||||||
|
provider = this._providers.get(providerId);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
provider = this._providers.get(DEFAULT_NOTEBOOK_PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
if (!provider) {
|
if (!provider) {
|
||||||
return Promise.reject(new Error(localize('notebookServiceNoProvider', 'Notebook provider does not exist'))).then();
|
return Promise.reject(new Error(localize('notebookServiceNoProvider', 'Notebook provider does not exist'))).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
return op(provider);
|
return op(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +140,6 @@ export class NotebookService implements INotebookService {
|
|||||||
}
|
}
|
||||||
return this._mimeRegistry;
|
return this._mimeRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BuiltinProvider implements INotebookProvider {
|
export class BuiltinProvider implements INotebookProvider {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { nb } from 'sqlops';
|
|||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
|
import { FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
|
|
||||||
const noKernel: string = localize('noKernel', 'No Kernel');
|
export const noKernel: string = localize('noKernel', 'No Kernel');
|
||||||
const runNotebookDisabled = localize('runNotebookDisabled', 'Cannot run cells as no kernel has been configured');
|
const runNotebookDisabled = localize('runNotebookDisabled', 'Cannot run cells as no kernel has been configured');
|
||||||
|
|
||||||
let noKernelSpec: nb.IKernelSpec = ({
|
let noKernelSpec: nb.IKernelSpec = ({
|
||||||
@@ -24,9 +24,9 @@ export class SessionManager implements nb.SessionManager {
|
|||||||
|
|
||||||
public get specs(): nb.IAllKernels {
|
public get specs(): nb.IAllKernels {
|
||||||
let allKernels: nb.IAllKernels = {
|
let allKernels: nb.IAllKernels = {
|
||||||
defaultKernel: noKernel,
|
defaultKernel: noKernel,
|
||||||
kernels: [noKernelSpec]
|
kernels: [noKernelSpec]
|
||||||
};
|
};
|
||||||
return allKernels;
|
return allKernels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ export class SessionManager implements nb.SessionManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmptySession implements nb.ISession {
|
export class EmptySession implements nb.ISession {
|
||||||
private _kernel: EmptyKernel;
|
private _kernel: EmptyKernel;
|
||||||
private _defaultKernelLoaded = false;
|
private _defaultKernelLoaded = false;
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ class EmptyKernel implements nb.IKernel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmptyFuture implements FutureInternal {
|
export class EmptyFuture implements FutureInternal {
|
||||||
|
|
||||||
|
|
||||||
get inProgress(): boolean {
|
get inProgress(): boolean {
|
||||||
|
|||||||
20
src/sql/sqlops.d.ts
vendored
20
src/sql/sqlops.d.ts
vendored
@@ -214,6 +214,7 @@ declare module 'sqlops' {
|
|||||||
providerName: string;
|
providerName: string;
|
||||||
saveProfile: boolean;
|
saveProfile: boolean;
|
||||||
id: string;
|
id: string;
|
||||||
|
azureTenantId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1299,15 +1300,16 @@ declare module 'sqlops' {
|
|||||||
lastRun: string;
|
lastRun: string;
|
||||||
nextRun: string;
|
nextRun: string;
|
||||||
jobId: string;
|
jobId: string;
|
||||||
EmailLevel: JobCompletionActionCondition;
|
startStepId: number;
|
||||||
PageLevel: JobCompletionActionCondition;
|
emailLevel: JobCompletionActionCondition;
|
||||||
EventLogLevel: JobCompletionActionCondition;
|
pageLevel: JobCompletionActionCondition;
|
||||||
DeleteLevel: JobCompletionActionCondition;
|
eventLogLevel: JobCompletionActionCondition;
|
||||||
OperatorToEmail: string;
|
deleteLevel: JobCompletionActionCondition;
|
||||||
OperatorToPage: string;
|
operatorToEmail: string;
|
||||||
JobSteps: AgentJobStepInfo[];
|
operatorToPage: string;
|
||||||
JobSchedules: AgentJobScheduleInfo[];
|
jobSteps: AgentJobStepInfo[];
|
||||||
Alerts: AgentAlertInfo[];
|
jobSchedules: AgentJobScheduleInfo[];
|
||||||
|
alerts: AgentAlertInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentJobScheduleInfo {
|
export interface AgentJobScheduleInfo {
|
||||||
|
|||||||
307
src/sql/sqlops.proposed.d.ts
vendored
307
src/sql/sqlops.proposed.d.ts
vendored
@@ -839,7 +839,7 @@ declare module 'sqlops' {
|
|||||||
* Create a dialog with the given title
|
* Create a dialog with the given title
|
||||||
* @param title The title of the dialog, displayed at the top
|
* @param title The title of the dialog, displayed at the top
|
||||||
*/
|
*/
|
||||||
export function createDialog(title: string): Dialog;
|
export function createDialog(title: string, dialogName?: string): Dialog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a dialog tab which can be included as part of the content of a dialog
|
* Create a dialog tab which can be included as part of the content of a dialog
|
||||||
@@ -951,6 +951,12 @@ declare module 'sqlops' {
|
|||||||
*/
|
*/
|
||||||
message: DialogMessage;
|
message: DialogMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the dialog name when opening
|
||||||
|
* the dialog for telemetry
|
||||||
|
*/
|
||||||
|
dialogName?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a callback that will be called when the user tries to click done. Only
|
* Register a callback that will be called when the user tries to click done. Only
|
||||||
* one callback can be registered at once, so each registration call will clear
|
* one callback can be registered at once, so each registration call will clear
|
||||||
@@ -1368,6 +1374,282 @@ declare module 'sqlops' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export namespace nb {
|
export namespace nb {
|
||||||
|
/**
|
||||||
|
* All notebook documents currently known to the system.
|
||||||
|
*
|
||||||
|
* @readonly
|
||||||
|
*/
|
||||||
|
export let notebookDocuments: NotebookDocument[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently active Notebook editor or `undefined`. The active editor is the one
|
||||||
|
* that currently has focus or, when none has focus, the one that has changed
|
||||||
|
* input most recently.
|
||||||
|
*/
|
||||||
|
export let activeNotebookEditor: NotebookEditor | undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently visible editors or an empty array.
|
||||||
|
*/
|
||||||
|
export let visibleNotebookEditors: NotebookEditor[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is emitted when a [notebook document](#NotebookDocument) is opened.
|
||||||
|
*
|
||||||
|
* To add an event listener when a visible text document is opened, use the [TextEditor](#TextEditor) events in the
|
||||||
|
* [window](#window) namespace. Note that:
|
||||||
|
*
|
||||||
|
* - The event is emitted before the [document](#NotebookDocument) is updated in the
|
||||||
|
* [active notebook editor](#nb.activeNotebookEditor)
|
||||||
|
* - When a [notebook document](#NotebookDocument) is already open (e.g.: open in another visible notebook editor) this event is not emitted
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const onDidOpenNotebookDocument: vscode.Event<NotebookDocument>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event that is emitted when a [notebook's](#NotebookDocument) cell contents are changed.
|
||||||
|
*/
|
||||||
|
export const onDidChangeNotebookCell: vscode.Event<NotebookCellChangeEvent>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the given document in a notebook editor. A [column](#ViewColumn) can be provided
|
||||||
|
* to control where the editor is being shown. Might change the [active editor](#nb.activeNotebookEditor).
|
||||||
|
*
|
||||||
|
* The document is denoted by an [uri](#Uri). Depending on the [scheme](#Uri.scheme) the
|
||||||
|
* following rules apply:
|
||||||
|
* `file`-scheme: Open a file on disk, will be rejected if the file does not exist or cannot be loaded.
|
||||||
|
* `untitled`-scheme: A new file that should be saved on disk, e.g. `untitled:c:\frodo\new.js`. The language
|
||||||
|
* will be derived from the file name.
|
||||||
|
* For all other schemes the registered notebook providers are consulted.
|
||||||
|
*
|
||||||
|
* @param document A document to be shown.
|
||||||
|
* @param column A view column in which the [editor](#NotebookEditor) should be shown. The default is the [active](#ViewColumn.Active), other values
|
||||||
|
* are adjusted to be `Min(column, columnCount + 1)`, the [active](#ViewColumn.Active)-column is not adjusted. Use [`ViewColumn.Beside`](#ViewColumn.Beside)
|
||||||
|
* to open the editor to the side of the currently active one.
|
||||||
|
* @param preserveFocus When `true` the editor will not take focus.
|
||||||
|
* @return A promise that resolves to a [notebook editor](#NotebookEditor).
|
||||||
|
*/
|
||||||
|
export function showNotebookDocument(uri: vscode.Uri, showOptions?: NotebookShowOptions): Thenable<NotebookEditor>;
|
||||||
|
|
||||||
|
export interface NotebookDocument {
|
||||||
|
/**
|
||||||
|
* The associated uri for this notebook document.
|
||||||
|
*
|
||||||
|
* *Note* that most documents use the `file`-scheme, which means they are files on disk. However, **not** all documents are
|
||||||
|
* saved on disk and therefore the `scheme` must be checked before trying to access the underlying file or siblings on disk.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
readonly uri: vscode.Uri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The file system path of the associated resource. Shorthand
|
||||||
|
* notation for [TextDocument.uri.fsPath](#TextDocument.uri). Independent of the uri scheme.
|
||||||
|
*/
|
||||||
|
readonly fileName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is this document representing an untitled file which has never been saved yet. *Note* that
|
||||||
|
* this does not mean the document will be saved to disk, use [`uri.scheme`](#Uri.scheme)
|
||||||
|
* to figure out where a document will be [saved](#FileSystemProvider), e.g. `file`, `ftp` etc.
|
||||||
|
*/
|
||||||
|
readonly isUntitled: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The identifier of the Notebook provider associated with this document.
|
||||||
|
*/
|
||||||
|
readonly providerId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `true` if there are unpersisted changes.
|
||||||
|
*/
|
||||||
|
readonly isDirty: boolean;
|
||||||
|
/**
|
||||||
|
* `true` if the document have been closed. A closed document isn't synchronized anymore
|
||||||
|
* and won't be re-used when the same resource is opened again.
|
||||||
|
*/
|
||||||
|
readonly isClosed: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All cells.
|
||||||
|
*/
|
||||||
|
readonly cells: NotebookCell[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the underlying file.
|
||||||
|
*
|
||||||
|
* @return A promise that will resolve to true when the file
|
||||||
|
* has been saved. If the file was not dirty or the save failed,
|
||||||
|
* will return false.
|
||||||
|
*/
|
||||||
|
save(): Thenable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure a cell range is completely contained in this document.
|
||||||
|
*
|
||||||
|
* @param range A cell range.
|
||||||
|
* @return The given range or a new, adjusted range.
|
||||||
|
*/
|
||||||
|
validateCellRange(range: CellRange): CellRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A cell range represents an ordered pair of two positions in a list of cells.
|
||||||
|
* It is guaranteed that [start](#CellRange.start).isBeforeOrEqual([end](#CellRange.end))
|
||||||
|
*
|
||||||
|
* CellRange objects are __immutable__.
|
||||||
|
*/
|
||||||
|
export class CellRange {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The start index. It is before or equal to [end](#CellRange.end).
|
||||||
|
*/
|
||||||
|
readonly start: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end index. It is after or equal to [start](#CellRange.start).
|
||||||
|
*/
|
||||||
|
readonly end: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new range from two positions. If `start` is not
|
||||||
|
* before or equal to `end`, the values will be swapped.
|
||||||
|
*
|
||||||
|
* @param start A number.
|
||||||
|
* @param end A number.
|
||||||
|
*/
|
||||||
|
constructor(start: number, end: number);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotebookEditor {
|
||||||
|
/**
|
||||||
|
* The document associated with this editor. The document will be the same for the entire lifetime of this editor.
|
||||||
|
*/
|
||||||
|
readonly document: NotebookDocument;
|
||||||
|
/**
|
||||||
|
* The column in which this editor shows. Will be `undefined` in case this
|
||||||
|
* isn't one of the main editors, e.g an embedded editor, or when the editor
|
||||||
|
* column is larger than three.
|
||||||
|
*/
|
||||||
|
viewColumn?: vscode.ViewColumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform an edit on the document associated with this notebook editor.
|
||||||
|
*
|
||||||
|
* The given callback-function is invoked with an [edit-builder](#NotebookEditorEdit) which must
|
||||||
|
* be used to make edits. Note that the edit-builder is only valid while the
|
||||||
|
* callback executes.
|
||||||
|
*
|
||||||
|
* @param callback A function which can create edits using an [edit-builder](#NotebookEditorEdit).
|
||||||
|
* @param options The undo/redo behavior around this edit. By default, undo stops will be created before and after this edit.
|
||||||
|
* @return A promise that resolves with a value indicating if the edits could be applied.
|
||||||
|
*/
|
||||||
|
edit(callback: (editBuilder: NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotebookCell {
|
||||||
|
contents: ICellContents;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotebookShowOptions {
|
||||||
|
/**
|
||||||
|
* An optional view column in which the [editor](#NotebookEditor) should be shown.
|
||||||
|
* The default is the [active](#ViewColumn.Active), other values are adjusted to
|
||||||
|
* be `Min(column, columnCount + 1)`, the [active](#ViewColumn.Active)-column is
|
||||||
|
* not adjusted. Use [`ViewColumn.Beside`](#ViewColumn.Beside) to open the
|
||||||
|
* editor to the side of the currently active one.
|
||||||
|
*/
|
||||||
|
viewColumn?: vscode.ViewColumn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional flag that when `true` will stop the [editor](#NotebookEditor) from taking focus.
|
||||||
|
*/
|
||||||
|
preserveFocus?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional flag that controls if an [editor](#NotebookEditor)-tab will be replaced
|
||||||
|
* with the next editor or if it will be kept.
|
||||||
|
*/
|
||||||
|
preview?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional string indicating which notebook provider to initially use
|
||||||
|
*/
|
||||||
|
providerId?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional ID indicating the initial connection to use for this editor
|
||||||
|
*/
|
||||||
|
connectionId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an event describing the change in a [notebook documents's cells](#NotebookDocument.cells).
|
||||||
|
*/
|
||||||
|
export interface NotebookCellChangeEvent {
|
||||||
|
/**
|
||||||
|
* The [notebook document](#NotebookDocument) for which the selections have changed.
|
||||||
|
*/
|
||||||
|
notebook: NotebookDocument;
|
||||||
|
/**
|
||||||
|
* The new value for the [notebook documents's cells](#NotebookDocument.cells).
|
||||||
|
*/
|
||||||
|
cell: NotebookCell[];
|
||||||
|
/**
|
||||||
|
* The [change kind](#TextEditorSelectionChangeKind) which has triggered this
|
||||||
|
* event. Can be `undefined`.
|
||||||
|
*/
|
||||||
|
kind?: vscode.TextEditorSelectionChangeKind;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A complex edit that will be applied in one transaction on a TextEditor.
|
||||||
|
* This holds a description of the edits and if the edits are valid (i.e. no overlapping regions, document was not changed in the meantime, etc.)
|
||||||
|
* they can be applied on a [document](#TextDocument) associated with a [text editor](#TextEditor).
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface NotebookEditorEdit {
|
||||||
|
/**
|
||||||
|
* Replace a cell range with a new cell.
|
||||||
|
*
|
||||||
|
* @param location The range this operation should remove.
|
||||||
|
* @param value The new cell this operation should insert after removing `location`.
|
||||||
|
*/
|
||||||
|
replace(location: number | CellRange, value: ICellContents): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a cell (optionally) at a specific index. Any index outside of the length of the cells
|
||||||
|
* will result in the cell being added at the end.
|
||||||
|
*
|
||||||
|
* @param index The position where the new text should be inserted.
|
||||||
|
* @param value The new text this operation should insert.
|
||||||
|
*/
|
||||||
|
insertCell(value: ICellContents, index?: number): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a certain cell.
|
||||||
|
*
|
||||||
|
* @param index The index of the cell to remove.
|
||||||
|
*/
|
||||||
|
deleteCell(index: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a notebook provider. The supported file types handled by this
|
||||||
|
* provider are defined in the `package.json:
|
||||||
|
* ```json
|
||||||
|
* {
|
||||||
|
* "contributes": {
|
||||||
|
* "notebook.providers": [{
|
||||||
|
* "provider": "providername",
|
||||||
|
* "fileExtensions": ["FILEEXT"]
|
||||||
|
* }]
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
* @export
|
||||||
|
* @param {NotebookProvider} provider
|
||||||
|
* @returns {vscode.Disposable}
|
||||||
|
*/
|
||||||
export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable;
|
export function registerNotebookProvider(provider: NotebookProvider): vscode.Disposable;
|
||||||
|
|
||||||
export interface NotebookProvider {
|
export interface NotebookProvider {
|
||||||
@@ -1431,7 +1713,7 @@ declare module 'sqlops' {
|
|||||||
/* Reads contents from a Uri representing a local or remote notebook and returns a
|
/* Reads contents from a Uri representing a local or remote notebook and returns a
|
||||||
* JSON object containing the cells and metadata about the notebook
|
* JSON object containing the cells and metadata about the notebook
|
||||||
*/
|
*/
|
||||||
getNotebookContents(notebookUri: vscode.Uri): Thenable<INotebook>;
|
getNotebookContents(notebookUri: vscode.Uri): Thenable<INotebookContents>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a file.
|
* Save a file.
|
||||||
@@ -1443,12 +1725,19 @@ declare module 'sqlops' {
|
|||||||
* @returns A thenable which resolves with the file content model when the
|
* @returns A thenable which resolves with the file content model when the
|
||||||
* file is saved.
|
* file is saved.
|
||||||
*/
|
*/
|
||||||
save(notebookUri: vscode.Uri, notebook: INotebook): Thenable<INotebook>;
|
save(notebookUri: vscode.Uri, notebook: INotebookContents): Thenable<INotebookContents>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INotebook {
|
|
||||||
|
|
||||||
readonly cells: ICell[];
|
/**
|
||||||
|
* Interface defining the file format contents of a notebook, usually in a serializable
|
||||||
|
* format. This interface does not have any methods for manipulating or interacting
|
||||||
|
* with a notebook object.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface INotebookContents {
|
||||||
|
|
||||||
|
readonly cells: ICellContents[];
|
||||||
readonly metadata: INotebookMetadata;
|
readonly metadata: INotebookMetadata;
|
||||||
readonly nbformat: number;
|
readonly nbformat: number;
|
||||||
readonly nbformat_minor: number;
|
readonly nbformat_minor: number;
|
||||||
@@ -1477,7 +1766,13 @@ declare module 'sqlops' {
|
|||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICell {
|
/**
|
||||||
|
* Interface defining the file format contents of a notebook cell, usually in a serializable
|
||||||
|
* format. This interface does not have any methods for manipulating or interacting
|
||||||
|
* with a cell object.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface ICellContents {
|
||||||
cell_type: CellType;
|
cell_type: CellType;
|
||||||
source: string | string[];
|
source: string | string[];
|
||||||
metadata: {
|
metadata: {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import { nb } from 'sqlops';
|
||||||
import { TreeItem } from 'vs/workbench/api/node/extHostTypes';
|
import { TreeItem } from 'vs/workbench/api/node/extHostTypes';
|
||||||
|
|
||||||
// SQL added extension host types
|
// SQL added extension host types
|
||||||
@@ -458,3 +459,43 @@ export interface INotebookFutureDone {
|
|||||||
succeeded: boolean;
|
succeeded: boolean;
|
||||||
rejectReason: string;
|
rejectReason: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICellRange {
|
||||||
|
readonly start: number;
|
||||||
|
readonly end: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CellRange {
|
||||||
|
|
||||||
|
protected _start: number;
|
||||||
|
protected _end: number;
|
||||||
|
|
||||||
|
get start(): number {
|
||||||
|
return this._start;
|
||||||
|
}
|
||||||
|
|
||||||
|
get end(): number {
|
||||||
|
return this._end;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(start: number, end: number) {
|
||||||
|
if (typeof(start) !== 'number' || typeof(start) !== 'number' || start < 0 || end < 0) {
|
||||||
|
throw new Error('Invalid arguments');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logic taken from range handling.
|
||||||
|
if (start <= end) {
|
||||||
|
this._start = start;
|
||||||
|
this._end = end;
|
||||||
|
} else {
|
||||||
|
this._start = end;
|
||||||
|
this._end = start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISingleNotebookEditOperation {
|
||||||
|
range: ICellRange;
|
||||||
|
cell: Partial<nb.ICellContents>;
|
||||||
|
forceMoveMarkers: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import * as sqlops from 'sqlops';
|
|||||||
|
|
||||||
import { SqlMainContext, ExtHostModelViewDialogShape, MainThreadModelViewDialogShape, ExtHostModelViewShape, ExtHostBackgroundTaskManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
import { SqlMainContext, ExtHostModelViewDialogShape, MainThreadModelViewDialogShape, ExtHostModelViewShape, ExtHostBackgroundTaskManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import { Inject } from '@angular/core';
|
||||||
|
|
||||||
const DONE_LABEL = nls.localize('dialogDoneLabel', 'Done');
|
const DONE_LABEL = nls.localize('dialogDoneLabel', 'Done');
|
||||||
const CANCEL_LABEL = nls.localize('dialogCancelLabel', 'Cancel');
|
const CANCEL_LABEL = nls.localize('dialogCancelLabel', 'Cancel');
|
||||||
@@ -125,6 +127,7 @@ class DialogImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdi
|
|||||||
private _message: sqlops.window.modelviewdialog.DialogMessage;
|
private _message: sqlops.window.modelviewdialog.DialogMessage;
|
||||||
private _closeValidator: () => boolean | Thenable<boolean>;
|
private _closeValidator: () => boolean | Thenable<boolean>;
|
||||||
private _operationHandler: BackgroundOperationHandler;
|
private _operationHandler: BackgroundOperationHandler;
|
||||||
|
private _dialogName: string;
|
||||||
|
|
||||||
constructor(extHostModelViewDialog: ExtHostModelViewDialog,
|
constructor(extHostModelViewDialog: ExtHostModelViewDialog,
|
||||||
extHostModelView: ExtHostModelViewShape,
|
extHostModelView: ExtHostModelViewShape,
|
||||||
@@ -157,6 +160,14 @@ class DialogImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdi
|
|||||||
this._extHostModelViewDialog.updateDialogContent(this);
|
this._extHostModelViewDialog.updateDialogContent(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get dialogName(): string {
|
||||||
|
return this._dialogName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set dialogName(value: string) {
|
||||||
|
this._dialogName = value;
|
||||||
|
}
|
||||||
|
|
||||||
public registerCloseValidator(validator: () => boolean | Thenable<boolean>): void {
|
public registerCloseValidator(validator: () => boolean | Thenable<boolean>): void {
|
||||||
this._closeValidator = validator;
|
this._closeValidator = validator;
|
||||||
}
|
}
|
||||||
@@ -503,7 +514,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
|||||||
public openDialog(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
public openDialog(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
||||||
let handle = this.getHandle(dialog);
|
let handle = this.getHandle(dialog);
|
||||||
this.updateDialogContent(dialog);
|
this.updateDialogContent(dialog);
|
||||||
this._proxy.$openDialog(handle);
|
dialog.dialogName ? this._proxy.$openDialog(handle, dialog.dialogName) :
|
||||||
|
this._proxy.$openDialog(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public closeDialog(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
public closeDialog(dialog: sqlops.window.modelviewdialog.Dialog): void {
|
||||||
@@ -560,8 +572,11 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
|
|||||||
this._onClickCallbacks.set(handle, callback);
|
this._onClickCallbacks.set(handle, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public createDialog(title: string, extensionLocation?: URI): sqlops.window.modelviewdialog.Dialog {
|
public createDialog(title: string, dialogName?: string, extensionLocation?: URI): sqlops.window.modelviewdialog.Dialog {
|
||||||
let dialog = new DialogImpl(this, this._extHostModelView, this._extHostTaskManagement, extensionLocation);
|
let dialog = new DialogImpl(this, this._extHostModelView, this._extHostTaskManagement, extensionLocation);
|
||||||
|
if (dialogName) {
|
||||||
|
dialog.dialogName = dialogName;
|
||||||
|
}
|
||||||
dialog.title = title;
|
dialog.title = title;
|
||||||
dialog.handle = this.getHandle(dialog);
|
dialog.handle = this.getHandle(dialog);
|
||||||
return dialog;
|
return dialog;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import URI, { UriComponents } from 'vs/base/common/uri';
|
|||||||
|
|
||||||
import { ExtHostNotebookShape, MainThreadNotebookShape, SqlMainContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
import { ExtHostNotebookShape, MainThreadNotebookShape, SqlMainContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
import { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails, FutureMessageType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails, FutureMessageType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
|
|
||||||
type Adapter = sqlops.nb.NotebookProvider | sqlops.nb.NotebookManager | sqlops.nb.ISession | sqlops.nb.IKernel | sqlops.nb.IFuture;
|
type Adapter = sqlops.nb.NotebookProvider | sqlops.nb.NotebookManager | sqlops.nb.ISession | sqlops.nb.IKernel | sqlops.nb.IFuture;
|
||||||
|
|
||||||
@@ -23,6 +24,12 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
|||||||
|
|
||||||
private readonly _proxy: MainThreadNotebookShape;
|
private readonly _proxy: MainThreadNotebookShape;
|
||||||
private _adapters = new Map<number, Adapter>();
|
private _adapters = new Map<number, Adapter>();
|
||||||
|
private _onDidOpenNotebook = new Emitter<sqlops.nb.NotebookDocument>();
|
||||||
|
private _onDidChangeNotebookCell = new Emitter<sqlops.nb.NotebookCellChangeEvent>();
|
||||||
|
|
||||||
|
public readonly onDidOpenNotebookDocument: Event<sqlops.nb.NotebookDocument> = this._onDidOpenNotebook.event;
|
||||||
|
public readonly onDidChangeNotebookCell: Event<sqlops.nb.NotebookCellChangeEvent> = this._onDidChangeNotebookCell.event;
|
||||||
|
|
||||||
// Notebook URI to manager lookup.
|
// Notebook URI to manager lookup.
|
||||||
constructor(_mainContext: IMainContext) {
|
constructor(_mainContext: IMainContext) {
|
||||||
this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadNotebook);
|
this._proxy = _mainContext.getProxy(SqlMainContext.MainThreadNotebook);
|
||||||
@@ -63,11 +70,11 @@ export class ExtHostNotebook implements ExtHostNotebookShape {
|
|||||||
return this._withServerManager(managerHandle, (serverManager) => serverManager.stopServer());
|
return this._withServerManager(managerHandle, (serverManager) => serverManager.stopServer());
|
||||||
}
|
}
|
||||||
|
|
||||||
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebook> {
|
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebookContents> {
|
||||||
return this._withContentManager(managerHandle, (contentManager) => contentManager.getNotebookContents(URI.revive(notebookUri)));
|
return this._withContentManager(managerHandle, (contentManager) => contentManager.getNotebookContents(URI.revive(notebookUri)));
|
||||||
}
|
}
|
||||||
|
|
||||||
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook> {
|
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebookContents): Thenable<sqlops.nb.INotebookContents> {
|
||||||
return this._withContentManager(managerHandle, (contentManager) => contentManager.save(URI.revive(notebookUri), notebook));
|
return this._withContentManager(managerHandle, (contentManager) => contentManager.save(URI.revive(notebookUri), notebook));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
101
src/sql/workbench/api/node/extHostNotebookDocumentData.ts
Normal file
101
src/sql/workbench/api/node/extHostNotebookDocumentData.ts
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import URI from 'vs/base/common/uri';
|
||||||
|
import { ok } from 'vs/base/common/assert';
|
||||||
|
import { Schemas } from 'vs/base/common/network';
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
|
||||||
|
import { MainThreadNotebookDocumentsAndEditorsShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
|
import { CellRange } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
|
||||||
|
|
||||||
|
export class ExtHostNotebookDocumentData implements IDisposable {
|
||||||
|
private _document: sqlops.nb.NotebookDocument;
|
||||||
|
private _cells: sqlops.nb.NotebookCell[];
|
||||||
|
private _isDisposed: boolean = false;
|
||||||
|
|
||||||
|
constructor(private readonly _proxy: MainThreadNotebookDocumentsAndEditorsShape,
|
||||||
|
private readonly _uri: URI,
|
||||||
|
private readonly _providerId: string,
|
||||||
|
private _isDirty: boolean
|
||||||
|
) {
|
||||||
|
// TODO add cell mapping support
|
||||||
|
this._cells = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
// we don't really dispose documents but let
|
||||||
|
// extensions still read from them. some
|
||||||
|
// operations, live saving, will now error tho
|
||||||
|
ok(!this._isDisposed);
|
||||||
|
this._isDisposed = true;
|
||||||
|
this._isDirty = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get document(): sqlops.nb.NotebookDocument {
|
||||||
|
if (!this._document) {
|
||||||
|
const data = this;
|
||||||
|
this._document = {
|
||||||
|
get uri() { return data._uri; },
|
||||||
|
get fileName() { return data._uri.fsPath; },
|
||||||
|
get isUntitled() { return data._uri.scheme === Schemas.untitled; },
|
||||||
|
get providerId() { return data._providerId; },
|
||||||
|
get isClosed() { return data._isDisposed; },
|
||||||
|
get isDirty() { return data._isDirty; },
|
||||||
|
get cells() { return data._cells; },
|
||||||
|
save() { return data._save(); },
|
||||||
|
validateCellRange(range) { return data._validateRange(range); },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return Object.freeze(this._document);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _save(): Thenable<boolean> {
|
||||||
|
if (this._isDisposed) {
|
||||||
|
return TPromise.wrapError<boolean>(new Error('Document has been closed'));
|
||||||
|
}
|
||||||
|
return this._proxy.$trySaveDocument(this._uri);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- range math
|
||||||
|
|
||||||
|
private _validateRange(range: sqlops.nb.CellRange): sqlops.nb.CellRange {
|
||||||
|
if (!(range instanceof CellRange)) {
|
||||||
|
throw new Error('Invalid argument');
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = this._validateIndex(range.start);
|
||||||
|
let end = this._validateIndex(range.end);
|
||||||
|
|
||||||
|
if (start === range.start && end === range.end) {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
return new CellRange(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _validateIndex(index: number): number {
|
||||||
|
if (typeof(index) !== 'number') {
|
||||||
|
throw new Error('Invalid argument');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
index = 0;
|
||||||
|
} else if (this._cells.length > 0 && index > this._cells.length) {
|
||||||
|
// We allow off by 1 as end needs to be outside current length in order to
|
||||||
|
// handle replace scenario. Long term should consider different start vs end validation instead
|
||||||
|
index = this._cells.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
192
src/sql/workbench/api/node/extHostNotebookDocumentsAndEditors.ts
Normal file
192
src/sql/workbench/api/node/extHostNotebookDocumentsAndEditors.ts
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 vscode from 'vscode';
|
||||||
|
|
||||||
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
|
import { dispose } from 'vs/base/common/lifecycle';
|
||||||
|
import URI from 'vs/base/common/uri';
|
||||||
|
import { Disposable } from 'vs/workbench/api/node/extHostTypes';
|
||||||
|
import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||||
|
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||||
|
import { ok } from 'vs/base/common/assert';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SqlMainContext, INotebookDocumentsAndEditorsDelta, ExtHostNotebookDocumentsAndEditorsShape,
|
||||||
|
MainThreadNotebookDocumentsAndEditorsShape, INotebookShowOptions
|
||||||
|
} from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
|
import { ExtHostNotebookDocumentData } from 'sql/workbench/api/node/extHostNotebookDocumentData';
|
||||||
|
import { ExtHostNotebookEditor } from 'sql/workbench/api/node/extHostNotebookEditor';
|
||||||
|
|
||||||
|
|
||||||
|
export class ExtHostNotebookDocumentsAndEditors implements ExtHostNotebookDocumentsAndEditorsShape {
|
||||||
|
|
||||||
|
private _disposables: Disposable[] = [];
|
||||||
|
|
||||||
|
private _activeEditorId: string;
|
||||||
|
private _proxy: MainThreadNotebookDocumentsAndEditorsShape;
|
||||||
|
|
||||||
|
private readonly _editors = new Map<string, ExtHostNotebookEditor>();
|
||||||
|
private readonly _documents = new Map<string, ExtHostNotebookDocumentData>();
|
||||||
|
|
||||||
|
private readonly _onDidAddDocuments = new Emitter<ExtHostNotebookDocumentData[]>();
|
||||||
|
private readonly _onDidRemoveDocuments = new Emitter<ExtHostNotebookDocumentData[]>();
|
||||||
|
private readonly _onDidChangeVisibleNotebookEditors = new Emitter<ExtHostNotebookEditor[]>();
|
||||||
|
private readonly _onDidChangeActiveNotebookEditor = new Emitter<ExtHostNotebookEditor>();
|
||||||
|
|
||||||
|
readonly onDidAddDocuments: Event<ExtHostNotebookDocumentData[]> = this._onDidAddDocuments.event;
|
||||||
|
readonly onDidRemoveDocuments: Event<ExtHostNotebookDocumentData[]> = this._onDidRemoveDocuments.event;
|
||||||
|
readonly onDidChangeVisibleNotebookEditors: Event<ExtHostNotebookEditor[]> = this._onDidChangeVisibleNotebookEditors.event;
|
||||||
|
readonly onDidChangeActiveNotebookEditor: Event<ExtHostNotebookEditor> = this._onDidChangeActiveNotebookEditor.event;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _mainContext: IMainContext,
|
||||||
|
) {
|
||||||
|
if (this._mainContext) {
|
||||||
|
this._proxy = this._mainContext.getProxy(SqlMainContext.MainThreadNotebookDocumentsAndEditors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this._disposables = dispose(this._disposables);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region Main Thread accessible methods
|
||||||
|
$acceptDocumentsAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void {
|
||||||
|
|
||||||
|
const removedDocuments: ExtHostNotebookDocumentData[] = [];
|
||||||
|
const addedDocuments: ExtHostNotebookDocumentData[] = [];
|
||||||
|
const removedEditors: ExtHostNotebookEditor[] = [];
|
||||||
|
|
||||||
|
if (delta.removedDocuments) {
|
||||||
|
for (const uriComponent of delta.removedDocuments) {
|
||||||
|
const uri = URI.revive(uriComponent);
|
||||||
|
const id = uri.toString();
|
||||||
|
const data = this._documents.get(id);
|
||||||
|
this._documents.delete(id);
|
||||||
|
removedDocuments.push(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta.addedDocuments) {
|
||||||
|
for (const data of delta.addedDocuments) {
|
||||||
|
const resource = URI.revive(data.uri);
|
||||||
|
ok(!this._documents.has(resource.toString()), `document '${resource} already exists!'`);
|
||||||
|
|
||||||
|
const documentData = new ExtHostNotebookDocumentData(
|
||||||
|
this._proxy,
|
||||||
|
resource,
|
||||||
|
data.providerId,
|
||||||
|
data.isDirty
|
||||||
|
);
|
||||||
|
this._documents.set(resource.toString(), documentData);
|
||||||
|
addedDocuments.push(documentData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta.removedEditors) {
|
||||||
|
for (const id of delta.removedEditors) {
|
||||||
|
const editor = this._editors.get(id);
|
||||||
|
this._editors.delete(id);
|
||||||
|
removedEditors.push(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta.addedEditors) {
|
||||||
|
for (const data of delta.addedEditors) {
|
||||||
|
const resource = URI.revive(data.documentUri);
|
||||||
|
ok(this._documents.has(resource.toString()), `document '${resource}' does not exist`);
|
||||||
|
ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`);
|
||||||
|
|
||||||
|
const documentData = this._documents.get(resource.toString());
|
||||||
|
const editor = new ExtHostNotebookEditor(
|
||||||
|
this._mainContext.getProxy(SqlMainContext.MainThreadNotebookDocumentsAndEditors),
|
||||||
|
data.id,
|
||||||
|
documentData,
|
||||||
|
typeConverters.ViewColumn.to(data.editorPosition)
|
||||||
|
);
|
||||||
|
this._editors.set(data.id, editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta.newActiveEditor !== undefined) {
|
||||||
|
ok(delta.newActiveEditor === null || this._editors.has(delta.newActiveEditor), `active editor '${delta.newActiveEditor}' does not exist`);
|
||||||
|
this._activeEditorId = delta.newActiveEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(removedDocuments);
|
||||||
|
dispose(removedEditors);
|
||||||
|
|
||||||
|
// now that the internal state is complete, fire events
|
||||||
|
if (delta.removedDocuments) {
|
||||||
|
this._onDidRemoveDocuments.fire(removedDocuments);
|
||||||
|
}
|
||||||
|
if (delta.addedDocuments) {
|
||||||
|
this._onDidAddDocuments.fire(addedDocuments);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta.removedEditors || delta.addedEditors) {
|
||||||
|
this._onDidChangeVisibleNotebookEditors.fire(this.getAllEditors());
|
||||||
|
}
|
||||||
|
if (delta.newActiveEditor !== undefined) {
|
||||||
|
this._onDidChangeActiveNotebookEditor.fire(this.getActiveEditor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Extension accessible methods
|
||||||
|
showNotebookDocument(uri: vscode.Uri, showOptions: sqlops.nb.NotebookShowOptions): Thenable<sqlops.nb.NotebookEditor> {
|
||||||
|
return this.doShowNotebookDocument(uri, showOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doShowNotebookDocument(uri: vscode.Uri, showOptions: sqlops.nb.NotebookShowOptions): Promise<sqlops.nb.NotebookEditor> {
|
||||||
|
let options: INotebookShowOptions = {};
|
||||||
|
if (showOptions) {
|
||||||
|
options.preserveFocus = showOptions.preserveFocus;
|
||||||
|
options.preview = showOptions.preview;
|
||||||
|
options.position = showOptions.viewColumn;
|
||||||
|
options.providerId = showOptions.providerId;
|
||||||
|
options.connectionId = showOptions.connectionId;
|
||||||
|
}
|
||||||
|
let id = await this._proxy.$tryShowNotebookDocument(uri, options);
|
||||||
|
let editor = this.getEditor(id);
|
||||||
|
if (editor) {
|
||||||
|
return editor;
|
||||||
|
} else {
|
||||||
|
throw new Error(`Failed to show notebook document ${uri.toString()}, should show in editor #${id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getDocument(strUrl: string): ExtHostNotebookDocumentData {
|
||||||
|
return this._documents.get(strUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllDocuments(): ExtHostNotebookDocumentData[] {
|
||||||
|
const result: ExtHostNotebookDocumentData[] = [];
|
||||||
|
this._documents.forEach(data => result.push(data));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditor(id: string): ExtHostNotebookEditor {
|
||||||
|
return this._editors.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
getActiveEditor(): ExtHostNotebookEditor | undefined {
|
||||||
|
if (!this._activeEditorId) {
|
||||||
|
return undefined;
|
||||||
|
} else {
|
||||||
|
return this._editors.get(this._activeEditorId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllEditors(): ExtHostNotebookEditor[] {
|
||||||
|
const result: ExtHostNotebookEditor[] = [];
|
||||||
|
this._editors.forEach(data => result.push(data));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
}
|
||||||
210
src/sql/workbench/api/node/extHostNotebookEditor.ts
Normal file
210
src/sql/workbench/api/node/extHostNotebookEditor.ts
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 vscode from 'vscode';
|
||||||
|
|
||||||
|
import { ok } from 'vs/base/common/assert';
|
||||||
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { readonly } from 'vs/base/common/errors';
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
|
||||||
|
import { MainThreadNotebookDocumentsAndEditorsShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
|
import { ExtHostNotebookDocumentData } from 'sql/workbench/api/node/extHostNotebookDocumentData';
|
||||||
|
import { CellRange, ISingleNotebookEditOperation, ICellRange } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
|
||||||
|
export interface INotebookEditOperation {
|
||||||
|
range: sqlops.nb.CellRange;
|
||||||
|
cell: Partial<sqlops.nb.ICellContents>;
|
||||||
|
forceMoveMarkers: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotebookEditData {
|
||||||
|
documentVersionId: number;
|
||||||
|
edits: INotebookEditOperation[];
|
||||||
|
undoStopBefore: boolean;
|
||||||
|
undoStopAfter: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toICellRange(range: sqlops.nb.CellRange): ICellRange {
|
||||||
|
return {
|
||||||
|
start: range.start,
|
||||||
|
end: range.end
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotebookEditorEdit {
|
||||||
|
|
||||||
|
private readonly _document: sqlops.nb.NotebookDocument;
|
||||||
|
private readonly _documentVersionId: number;
|
||||||
|
private _collectedEdits: INotebookEditOperation[];
|
||||||
|
private readonly _undoStopBefore: boolean;
|
||||||
|
private readonly _undoStopAfter: boolean;
|
||||||
|
|
||||||
|
constructor(document: sqlops.nb.NotebookDocument, options: { undoStopBefore: boolean; undoStopAfter: boolean; }) {
|
||||||
|
this._document = document;
|
||||||
|
// TODO add version handling
|
||||||
|
this._documentVersionId = 0;
|
||||||
|
// this._documentVersionId = document.version;
|
||||||
|
this._collectedEdits = [];
|
||||||
|
this._undoStopBefore = options ? options.undoStopBefore : true;
|
||||||
|
this._undoStopAfter = options ? options.undoStopAfter : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalize(): INotebookEditData {
|
||||||
|
return {
|
||||||
|
documentVersionId: this._documentVersionId,
|
||||||
|
edits: this._collectedEdits,
|
||||||
|
undoStopBefore: this._undoStopBefore,
|
||||||
|
undoStopAfter: this._undoStopAfter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
replace(location: number | CellRange, value: Partial<sqlops.nb.ICellContents>): void {
|
||||||
|
let range: CellRange = this.getAsRange(location);
|
||||||
|
this._pushEdit(range, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getAsRange(location: number | CellRange): CellRange {
|
||||||
|
let range: CellRange = null;
|
||||||
|
if (typeof (location) === 'number') {
|
||||||
|
range = new CellRange(location, location+1);
|
||||||
|
}
|
||||||
|
else if (location instanceof CellRange) {
|
||||||
|
range = location;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new Error('Unrecognized location');
|
||||||
|
}
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
insertCell(value: Partial<sqlops.nb.ICellContents>, location?: number): void {
|
||||||
|
if (location === null || location === undefined) {
|
||||||
|
// If not specified, assume adding to end of list
|
||||||
|
location = this._document.cells.length - 1;
|
||||||
|
}
|
||||||
|
this._pushEdit(new CellRange(location, location), value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCell(index: number): void {
|
||||||
|
let range: CellRange = null;
|
||||||
|
|
||||||
|
if (typeof(index) === 'number') {
|
||||||
|
// Currently only allowing single-cell deletion.
|
||||||
|
// Do this by saying the range extends over 1 cell so on the main thread
|
||||||
|
// we can delete that cell, then handle insertions
|
||||||
|
range = new CellRange(index, index+1);
|
||||||
|
} else {
|
||||||
|
throw new Error('Unrecognized index');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._pushEdit(range, null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _pushEdit(range: sqlops.nb.CellRange, cell: Partial<sqlops.nb.ICellContents>, forceMoveMarkers: boolean): void {
|
||||||
|
let validRange = this._document.validateCellRange(range);
|
||||||
|
this._collectedEdits.push({
|
||||||
|
range: validRange,
|
||||||
|
cell: cell,
|
||||||
|
forceMoveMarkers: forceMoveMarkers
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ExtHostNotebookEditor implements sqlops.nb.NotebookEditor, IDisposable {
|
||||||
|
private _disposed: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private _proxy: MainThreadNotebookDocumentsAndEditorsShape,
|
||||||
|
private _id: string,
|
||||||
|
private readonly _documentData: ExtHostNotebookDocumentData,
|
||||||
|
private _viewColumn: vscode.ViewColumn
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
ok(!this._disposed);
|
||||||
|
this._disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get document(): sqlops.nb.NotebookDocument {
|
||||||
|
return this._documentData.document;
|
||||||
|
}
|
||||||
|
|
||||||
|
set document(value) {
|
||||||
|
throw readonly('document');
|
||||||
|
}
|
||||||
|
|
||||||
|
get viewColumn(): vscode.ViewColumn {
|
||||||
|
return this._viewColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
set viewColumn(value) {
|
||||||
|
throw readonly('viewColumn');
|
||||||
|
}
|
||||||
|
|
||||||
|
get id(): string {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
edit(callback: (editBuilder: sqlops.nb.NotebookEditorEdit) => void, options?: { undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable<boolean> {
|
||||||
|
if (this._disposed) {
|
||||||
|
return TPromise.wrapError<boolean>(new Error('NotebookEditor#edit not possible on closed editors'));
|
||||||
|
}
|
||||||
|
let edit = new NotebookEditorEdit(this._documentData.document, options);
|
||||||
|
callback(edit);
|
||||||
|
return this._applyEdit(edit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _applyEdit(editBuilder: NotebookEditorEdit): TPromise<boolean> {
|
||||||
|
let editData = editBuilder.finalize();
|
||||||
|
|
||||||
|
// return when there is nothing to do
|
||||||
|
if (editData.edits.length === 0) {
|
||||||
|
return TPromise.wrap(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the edits are not overlapping (i.e. illegal)
|
||||||
|
let editRanges = editData.edits.map(edit => edit.range);
|
||||||
|
|
||||||
|
// sort ascending (by end and then by start)
|
||||||
|
editRanges.sort((a, b) => {
|
||||||
|
if (a.end === b.end) {
|
||||||
|
return a.start - b.start;
|
||||||
|
}
|
||||||
|
return a.end - b.end;
|
||||||
|
});
|
||||||
|
|
||||||
|
// check that no edits are overlapping
|
||||||
|
for (let i = 0, count = editRanges.length - 1; i < count; i++) {
|
||||||
|
const rangeEnd = editRanges[i].end;
|
||||||
|
const nextRangeStart = editRanges[i + 1].start;
|
||||||
|
|
||||||
|
if (nextRangeStart < rangeEnd) {
|
||||||
|
// overlapping ranges
|
||||||
|
return TPromise.wrapError<boolean>(
|
||||||
|
new Error('Overlapping ranges are not allowed!')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare data for serialization
|
||||||
|
let edits: ISingleNotebookEditOperation[] = editData.edits.map((edit) => {
|
||||||
|
return {
|
||||||
|
range: toICellRange(edit.range),
|
||||||
|
cell: edit.cell,
|
||||||
|
forceMoveMarkers: edit.forceMoveMarkers
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._proxy.$tryApplyEdits(this._id, editData.documentVersionId, edits, {
|
||||||
|
undoStopBefore: editData.undoStopBefore,
|
||||||
|
undoStopAfter: editData.undoStopAfter
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import { ModelViewInput, ModelViewInputModel, ModeViewSaveHandler } from 'sql/pa
|
|||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
|
||||||
@extHostNamedCustomer(SqlMainContext.MainThreadModelViewDialog)
|
@extHostNamedCustomer(SqlMainContext.MainThreadModelViewDialog)
|
||||||
export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape {
|
export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape {
|
||||||
@@ -35,7 +36,8 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
|||||||
constructor(
|
constructor(
|
||||||
context: IExtHostContext,
|
context: IExtHostContext,
|
||||||
@IInstantiationService private _instatiationService: IInstantiationService,
|
@IInstantiationService private _instatiationService: IInstantiationService,
|
||||||
@IEditorService private _editorService: IEditorService
|
@IEditorService private _editorService: IEditorService,
|
||||||
|
@ITelemetryService private _telemetryService: ITelemetryService
|
||||||
) {
|
) {
|
||||||
this._proxy = context.getProxy(SqlExtHostContext.ExtHostModelViewDialog);
|
this._proxy = context.getProxy(SqlExtHostContext.ExtHostModelViewDialog);
|
||||||
this._dialogService = new CustomDialogService(_instatiationService);
|
this._dialogService = new CustomDialogService(_instatiationService);
|
||||||
@@ -68,9 +70,9 @@ export class MainThreadModelViewDialog implements MainThreadModelViewDialogShape
|
|||||||
return this._proxy.$handleSave(handle);
|
return this._proxy.$handleSave(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $openDialog(handle: number): Thenable<void> {
|
public $openDialog(handle: number, dialogName?: string): Thenable<void> {
|
||||||
let dialog = this.getDialog(handle);
|
let dialog = this.getDialog(handle);
|
||||||
this._dialogService.showDialog(dialog);
|
dialogName ? this._dialogService.showDialog(dialog, dialogName) : this._dialogService.showDialog(dialog);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,11 +153,11 @@ class ContentManagerWrapper implements sqlops.nb.ContentManager {
|
|||||||
|
|
||||||
constructor(private handle: number, private _proxy: Proxies) {
|
constructor(private handle: number, private _proxy: Proxies) {
|
||||||
}
|
}
|
||||||
getNotebookContents(notebookUri: URI): Thenable<sqlops.nb.INotebook> {
|
getNotebookContents(notebookUri: URI): Thenable<sqlops.nb.INotebookContents> {
|
||||||
return this._proxy.ext.$getNotebookContents(this.handle, notebookUri);
|
return this._proxy.ext.$getNotebookContents(this.handle, notebookUri);
|
||||||
}
|
}
|
||||||
|
|
||||||
save(path: URI, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook> {
|
save(path: URI, notebook: sqlops.nb.INotebookContents): Thenable<sqlops.nb.INotebookContents> {
|
||||||
return this._proxy.ext.$save(this.handle, path, notebook);
|
return this._proxy.ext.$save(this.handle, path, notebook);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,412 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import URI, { UriComponents } from 'vs/base/common/uri';
|
||||||
|
import { IExtHostContext, IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol';
|
||||||
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||||
|
import { viewColumnToEditorGroup } from 'vs/workbench/api/shared/editor';
|
||||||
|
|
||||||
|
import {
|
||||||
|
SqlMainContext, MainThreadNotebookDocumentsAndEditorsShape, SqlExtHostContext, ExtHostNotebookDocumentsAndEditorsShape,
|
||||||
|
INotebookDocumentsAndEditorsDelta, INotebookEditorAddData, INotebookShowOptions, INotebookModelAddedData
|
||||||
|
} from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||||
|
import { NotebookInputModel, NotebookInput } from 'sql/parts/notebook/notebookInput';
|
||||||
|
import { INotebookService, INotebookEditor } from 'sql/services/notebook/notebookService';
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
|
||||||
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
import { disposed } from 'vs/base/common/errors';
|
||||||
|
|
||||||
|
class MainThreadNotebookEditor extends Disposable {
|
||||||
|
|
||||||
|
constructor(public readonly editor: INotebookEditor) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get uri(): URI {
|
||||||
|
return this.editor.notebookParams.notebookUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get id(): string {
|
||||||
|
return this.editor.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isDirty(): boolean {
|
||||||
|
return this.editor.isDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get providerId(): string {
|
||||||
|
return this.editor.notebookParams.providerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public save(): Thenable<boolean> {
|
||||||
|
return this.editor.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public matches(input: NotebookInput): boolean {
|
||||||
|
if (!input) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return input === this.editor.notebookParams.input;
|
||||||
|
}
|
||||||
|
|
||||||
|
public applyEdits(versionIdCheck: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): boolean {
|
||||||
|
// TODO Handle version tracking
|
||||||
|
// if (this._model.getVersionId() !== versionIdCheck) {
|
||||||
|
// // throw new Error('Model has changed in the meantime!');
|
||||||
|
// // model changed in the meantime
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!this.editor) {
|
||||||
|
// console.warn('applyEdits on invisible editor');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle undo tracking
|
||||||
|
// if (opts.undoStopBefore) {
|
||||||
|
// this._codeEditor.pushUndoStop();
|
||||||
|
// }
|
||||||
|
|
||||||
|
this.editor.executeEdits(edits);
|
||||||
|
// if (opts.undoStopAfter) {
|
||||||
|
// this._codeEditor.pushUndoStop();
|
||||||
|
// }
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function wait(timeMs: number): Promise<void> {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, timeMs));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace mapset {
|
||||||
|
|
||||||
|
export function setValues<T>(set: Set<T>): T[] {
|
||||||
|
// return Array.from(set);
|
||||||
|
let ret: T[] = [];
|
||||||
|
set.forEach(v => ret.push(v));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapValues<T>(map: Map<any, T>): T[] {
|
||||||
|
// return Array.from(map.values());
|
||||||
|
let ret: T[] = [];
|
||||||
|
map.forEach(v => ret.push(v));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace delta {
|
||||||
|
|
||||||
|
export function ofSets<T>(before: Set<T>, after: Set<T>): { removed: T[], added: T[] } {
|
||||||
|
const removed: T[] = [];
|
||||||
|
const added: T[] = [];
|
||||||
|
before.forEach(element => {
|
||||||
|
if (!after.has(element)) {
|
||||||
|
removed.push(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
after.forEach(element => {
|
||||||
|
if (!before.has(element)) {
|
||||||
|
added.push(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { removed, added };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ofMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed: V[], added: V[] } {
|
||||||
|
const removed: V[] = [];
|
||||||
|
const added: V[] = [];
|
||||||
|
before.forEach((value, index) => {
|
||||||
|
if (!after.has(index)) {
|
||||||
|
removed.push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
after.forEach((value, index) => {
|
||||||
|
if (!before.has(index)) {
|
||||||
|
added.push(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { removed, added };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotebookEditorStateDelta {
|
||||||
|
|
||||||
|
readonly isEmpty: boolean;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly removedEditors: INotebookEditor[],
|
||||||
|
readonly addedEditors: INotebookEditor[],
|
||||||
|
readonly oldActiveEditor: string,
|
||||||
|
readonly newActiveEditor: string,
|
||||||
|
) {
|
||||||
|
this.isEmpty =
|
||||||
|
this.removedEditors.length === 0
|
||||||
|
&& this.addedEditors.length === 0
|
||||||
|
&& oldActiveEditor === newActiveEditor;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
let ret = 'NotebookEditorStateDelta\n';
|
||||||
|
ret += `\tRemoved Editors: [${this.removedEditors.map(e => e.id).join(', ')}]\n`;
|
||||||
|
ret += `\tAdded Editors: [${this.addedEditors.map(e => e.id).join(', ')}]\n`;
|
||||||
|
ret += `\tNew Active Editor: ${this.newActiveEditor}\n`;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NotebookEditorState {
|
||||||
|
|
||||||
|
static compute(before: NotebookEditorState, after: NotebookEditorState): NotebookEditorStateDelta {
|
||||||
|
if (!before) {
|
||||||
|
return new NotebookEditorStateDelta(
|
||||||
|
[], mapset.mapValues(after.textEditors),
|
||||||
|
undefined, after.activeEditor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const editorDelta = delta.ofMaps(before.textEditors, after.textEditors);
|
||||||
|
const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined;
|
||||||
|
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
|
||||||
|
|
||||||
|
return new NotebookEditorStateDelta(
|
||||||
|
editorDelta.removed, editorDelta.added,
|
||||||
|
oldActiveEditor, newActiveEditor
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly textEditors: Map<string, INotebookEditor>,
|
||||||
|
readonly activeEditor: string) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
class MainThreadNotebookDocumentAndEditorStateComputer extends Disposable {
|
||||||
|
|
||||||
|
private _currentState: NotebookEditorState;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _onDidChangeState: (delta: NotebookEditorStateDelta) => void,
|
||||||
|
@IEditorService private readonly _editorService: IEditorService,
|
||||||
|
@INotebookService private readonly _notebookService: INotebookService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this._register(this._editorService.onDidActiveEditorChange(this._updateState, this));
|
||||||
|
this._register(this._editorService.onDidVisibleEditorsChange(this._updateState, this));
|
||||||
|
this._register(this._notebookService.onNotebookEditorAdd(this._onDidAddEditor, this));
|
||||||
|
this._register(this._notebookService.onNotebookEditorRemove(this._onDidRemoveEditor, this));
|
||||||
|
|
||||||
|
this._updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDidAddEditor(e: INotebookEditor): void {
|
||||||
|
// TODO hook to cell change and other events
|
||||||
|
this._updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDidRemoveEditor(e: INotebookEditor): void {
|
||||||
|
// TODO remove event listeners
|
||||||
|
this._updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _updateState(): void {
|
||||||
|
// editor
|
||||||
|
const editors = new Map<string, INotebookEditor>();
|
||||||
|
let activeEditor: string = undefined;
|
||||||
|
|
||||||
|
for (const editor of this._notebookService.listNotebookEditors()) {
|
||||||
|
editors.set(editor.id, editor);
|
||||||
|
if (editor.isActive()) {
|
||||||
|
activeEditor = editor.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute new state and compare against old
|
||||||
|
const newState = new NotebookEditorState(editors, activeEditor);
|
||||||
|
const delta = NotebookEditorState.compute(this._currentState, newState);
|
||||||
|
if (!delta.isEmpty) {
|
||||||
|
this._currentState = newState;
|
||||||
|
this._onDidChangeState(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@extHostNamedCustomer(SqlMainContext.MainThreadNotebookDocumentsAndEditors)
|
||||||
|
export class MainThreadNotebookDocumentsAndEditors extends Disposable implements MainThreadNotebookDocumentsAndEditorsShape {
|
||||||
|
|
||||||
|
private _proxy: ExtHostNotebookDocumentsAndEditorsShape;
|
||||||
|
private _notebookEditors = new Map<string, MainThreadNotebookEditor>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
extHostContext: IExtHostContext,
|
||||||
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
|
@IEditorService private _editorService: IEditorService,
|
||||||
|
@IEditorGroupsService private _editorGroupService: IEditorGroupsService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
if (extHostContext) {
|
||||||
|
this._proxy = extHostContext.getProxy(SqlExtHostContext.ExtHostNotebookDocumentsAndEditors);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a state computer that actually tracks all required changes. This is hooked to onDelta which notifies extension host
|
||||||
|
this._register(this._instantiationService.createInstance(MainThreadNotebookDocumentAndEditorStateComputer, delta => this._onDelta(delta)));
|
||||||
|
}
|
||||||
|
|
||||||
|
//#region extension host callable APIs
|
||||||
|
$trySaveDocument(uri: UriComponents): Thenable<boolean> {
|
||||||
|
let uriString = URI.revive(uri).toString();
|
||||||
|
let editor = this._notebookEditors.get(uriString);
|
||||||
|
if (editor) {
|
||||||
|
return editor.save();
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): TPromise<string> {
|
||||||
|
return TPromise.wrap(this.doOpenEditor(resource, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): TPromise<boolean> {
|
||||||
|
let editor = this.getEditor(id);
|
||||||
|
if (!editor) {
|
||||||
|
return TPromise.wrapError<boolean>(disposed(`TextEditor(${id})`));
|
||||||
|
}
|
||||||
|
return TPromise.as(editor.applyEdits(modelVersionId, edits, opts));
|
||||||
|
}
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
private async doOpenEditor(resource: UriComponents, options: INotebookShowOptions): Promise<string> {
|
||||||
|
const uri = URI.revive(resource);
|
||||||
|
|
||||||
|
const editorOptions: ITextEditorOptions = {
|
||||||
|
preserveFocus: options.preserveFocus,
|
||||||
|
pinned: !options.preview
|
||||||
|
};
|
||||||
|
let model = new NotebookInputModel(uri, undefined, false, undefined);
|
||||||
|
let providerId = options.providerId;
|
||||||
|
if(!providerId)
|
||||||
|
{
|
||||||
|
// Ensure there is always a sensible provider ID for this file type
|
||||||
|
providerId = getProviderForFileName(uri.fsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
model.providerId = providerId;
|
||||||
|
let input = this._instantiationService.createInstance(NotebookInput, undefined, model);
|
||||||
|
|
||||||
|
let editor = await this._editorService.openEditor(input, editorOptions, viewColumnToEditorGroup(this._editorGroupService, options.position));
|
||||||
|
if (!editor) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return this.waitOnEditor(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async waitOnEditor(input: NotebookInput): Promise<string> {
|
||||||
|
let id: string = undefined;
|
||||||
|
let attemptsLeft = 10;
|
||||||
|
let timeoutMs = 20;
|
||||||
|
while (!id && attemptsLeft > 0) {
|
||||||
|
id = this.findNotebookEditorIdFor(input);
|
||||||
|
if (!id) {
|
||||||
|
await wait(timeoutMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
findNotebookEditorIdFor(input: NotebookInput): string {
|
||||||
|
let foundId: string = undefined;
|
||||||
|
this._notebookEditors.forEach(e => {
|
||||||
|
if (e.matches(input)) {
|
||||||
|
foundId = e.id;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return foundId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEditor(id: string): MainThreadNotebookEditor {
|
||||||
|
return this._notebookEditors.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDelta(delta: NotebookEditorStateDelta): void {
|
||||||
|
let removedEditors: string[] = [];
|
||||||
|
let removedDocuments: URI[] = [];
|
||||||
|
let addedEditors: MainThreadNotebookEditor[] = [];
|
||||||
|
|
||||||
|
// added editors
|
||||||
|
for (const editor of delta.addedEditors) {
|
||||||
|
const mainThreadEditor = new MainThreadNotebookEditor(editor);
|
||||||
|
|
||||||
|
this._notebookEditors.set(editor.id, mainThreadEditor);
|
||||||
|
addedEditors.push(mainThreadEditor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// removed editors
|
||||||
|
for (const { id } of delta.removedEditors) {
|
||||||
|
const mainThreadEditor = this._notebookEditors.get(id);
|
||||||
|
if (mainThreadEditor) {
|
||||||
|
removedDocuments.push(mainThreadEditor.uri);
|
||||||
|
mainThreadEditor.dispose();
|
||||||
|
this._notebookEditors.delete(id);
|
||||||
|
removedEditors.push(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let extHostDelta: INotebookDocumentsAndEditorsDelta = Object.create(null);
|
||||||
|
let empty = true;
|
||||||
|
if (delta.newActiveEditor !== undefined) {
|
||||||
|
empty = false;
|
||||||
|
extHostDelta.newActiveEditor = delta.newActiveEditor;
|
||||||
|
}
|
||||||
|
if (removedDocuments.length > 0) {
|
||||||
|
empty = false;
|
||||||
|
extHostDelta.removedDocuments = removedDocuments;
|
||||||
|
}
|
||||||
|
if (removedEditors.length > 0) {
|
||||||
|
empty = false;
|
||||||
|
extHostDelta.removedEditors = removedEditors;
|
||||||
|
}
|
||||||
|
if (delta.addedEditors.length > 0) {
|
||||||
|
empty = false;
|
||||||
|
extHostDelta.addedDocuments = [];
|
||||||
|
extHostDelta.addedEditors = [];
|
||||||
|
for (let editor of addedEditors) {
|
||||||
|
extHostDelta.addedEditors.push(this._toNotebookEditorAddData(editor));
|
||||||
|
// For now, add 1 document for each editor. In the future these may be trackable independently
|
||||||
|
extHostDelta.addedDocuments.push(this._toNotebookModelAddData(editor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty) {
|
||||||
|
this._proxy.$acceptDocumentsAndEditorsDelta(extHostDelta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toNotebookEditorAddData(editor: MainThreadNotebookEditor): INotebookEditorAddData {
|
||||||
|
let addData: INotebookEditorAddData = {
|
||||||
|
documentUri: editor.uri,
|
||||||
|
editorPosition: undefined,
|
||||||
|
id: editor.editor.id
|
||||||
|
};
|
||||||
|
return addData;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _toNotebookModelAddData(editor: MainThreadNotebookEditor): INotebookModelAddedData {
|
||||||
|
let addData: INotebookModelAddedData = {
|
||||||
|
uri: editor.uri,
|
||||||
|
isDirty: editor.isDirty,
|
||||||
|
providerId: editor.providerId
|
||||||
|
};
|
||||||
|
return addData;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ import { ExtHostModelViewTreeViews } from 'sql/workbench/api/node/extHostModelVi
|
|||||||
import { ExtHostQueryEditor } from 'sql/workbench/api/node/extHostQueryEditor';
|
import { ExtHostQueryEditor } from 'sql/workbench/api/node/extHostQueryEditor';
|
||||||
import { ExtHostBackgroundTaskManagement } from './extHostBackgroundTaskManagement';
|
import { ExtHostBackgroundTaskManagement } from './extHostBackgroundTaskManagement';
|
||||||
import { ExtHostNotebook } from 'sql/workbench/api/node/extHostNotebook';
|
import { ExtHostNotebook } from 'sql/workbench/api/node/extHostNotebook';
|
||||||
|
import { ExtHostNotebookDocumentsAndEditors } from 'sql/workbench/api/node/extHostNotebookDocumentsAndEditors';
|
||||||
|
|
||||||
export interface ISqlExtensionApiFactory {
|
export interface ISqlExtensionApiFactory {
|
||||||
vsCodeFactory(extension: IExtensionDescription): typeof vscode;
|
vsCodeFactory(extension: IExtensionDescription): typeof vscode;
|
||||||
@@ -75,6 +76,7 @@ export function createApiFactory(
|
|||||||
const extHostModelViewDialog = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewDialog, new ExtHostModelViewDialog(rpcProtocol, extHostModelView, extHostBackgroundTaskManagement));
|
const extHostModelViewDialog = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewDialog, new ExtHostModelViewDialog(rpcProtocol, extHostModelView, extHostBackgroundTaskManagement));
|
||||||
const extHostQueryEditor = rpcProtocol.set(SqlExtHostContext.ExtHostQueryEditor, new ExtHostQueryEditor(rpcProtocol));
|
const extHostQueryEditor = rpcProtocol.set(SqlExtHostContext.ExtHostQueryEditor, new ExtHostQueryEditor(rpcProtocol));
|
||||||
const extHostNotebook = rpcProtocol.set(SqlExtHostContext.ExtHostNotebook, new ExtHostNotebook(rpcProtocol));
|
const extHostNotebook = rpcProtocol.set(SqlExtHostContext.ExtHostNotebook, new ExtHostNotebook(rpcProtocol));
|
||||||
|
const extHostNotebookDocumentsAndEditors = rpcProtocol.set(SqlExtHostContext.ExtHostNotebookDocumentsAndEditors, new ExtHostNotebookDocumentsAndEditors(rpcProtocol));
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -346,8 +348,8 @@ export function createApiFactory(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const modelViewDialog: typeof sqlops.window.modelviewdialog = {
|
const modelViewDialog: typeof sqlops.window.modelviewdialog = {
|
||||||
createDialog(title: string): sqlops.window.modelviewdialog.Dialog {
|
createDialog(title: string, dialogName?: string): sqlops.window.modelviewdialog.Dialog {
|
||||||
return extHostModelViewDialog.createDialog(title, extension.extensionLocation);
|
return extHostModelViewDialog.createDialog(title, dialogName, extension.extensionLocation);
|
||||||
},
|
},
|
||||||
createTab(title: string): sqlops.window.modelviewdialog.DialogTab {
|
createTab(title: string): sqlops.window.modelviewdialog.DialogTab {
|
||||||
return extHostModelViewDialog.createTab(title, extension.extensionLocation);
|
return extHostModelViewDialog.createTab(title, extension.extensionLocation);
|
||||||
@@ -420,9 +422,28 @@ export function createApiFactory(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const nb = {
|
const nb = {
|
||||||
|
get notebookDocuments() {
|
||||||
|
return extHostNotebookDocumentsAndEditors.getAllDocuments().map(doc => doc.document);
|
||||||
|
},
|
||||||
|
get activeNotebookEditor() {
|
||||||
|
return extHostNotebookDocumentsAndEditors.getActiveEditor();
|
||||||
|
},
|
||||||
|
get visibleNotebookEditors() {
|
||||||
|
return extHostNotebookDocumentsAndEditors.getAllEditors();
|
||||||
|
},
|
||||||
|
get onDidOpenNotebookDocument() {
|
||||||
|
return extHostNotebook.onDidOpenNotebookDocument;
|
||||||
|
},
|
||||||
|
get onDidChangeNotebookCell() {
|
||||||
|
return extHostNotebook.onDidChangeNotebookCell;
|
||||||
|
},
|
||||||
|
showNotebookDocument(uri: vscode.Uri, showOptions: sqlops.nb.NotebookShowOptions) {
|
||||||
|
return extHostNotebookDocumentsAndEditors.showNotebookDocument(uri, showOptions);
|
||||||
|
},
|
||||||
registerNotebookProvider(provider: sqlops.nb.NotebookProvider): vscode.Disposable {
|
registerNotebookProvider(provider: sqlops.nb.NotebookProvider): vscode.Disposable {
|
||||||
return extHostNotebook.registerNotebookProvider(provider);
|
return extHostNotebook.registerNotebookProvider(provider);
|
||||||
}
|
},
|
||||||
|
CellRange: sqlExtHostTypes.CellRange
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import 'sql/workbench/api/node/mainThreadQueryEditor';
|
|||||||
import 'sql/workbench/api/node/mainThreadModelView';
|
import 'sql/workbench/api/node/mainThreadModelView';
|
||||||
import 'sql/workbench/api/node/mainThreadModelViewDialog';
|
import 'sql/workbench/api/node/mainThreadModelViewDialog';
|
||||||
import 'sql/workbench/api/node/mainThreadNotebook';
|
import 'sql/workbench/api/node/mainThreadNotebook';
|
||||||
|
import 'sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors';
|
||||||
import 'sql/workbench/api/node/mainThreadAccountManagement';
|
import 'sql/workbench/api/node/mainThreadAccountManagement';
|
||||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||||
|
|
||||||
|
|||||||
@@ -21,8 +21,10 @@ import { ITreeComponentItem } from 'sql/workbench/common/views';
|
|||||||
import { ITaskHandlerDescription } from 'sql/platform/tasks/common/tasks';
|
import { ITaskHandlerDescription } from 'sql/platform/tasks/common/tasks';
|
||||||
import {
|
import {
|
||||||
IItemConfig, ModelComponentTypes, IComponentShape, IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails,
|
IItemConfig, ModelComponentTypes, IComponentShape, IModelViewDialogDetails, IModelViewTabDetails, IModelViewButtonDetails,
|
||||||
IModelViewWizardDetails, IModelViewWizardPageDetails, INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails, FutureMessageType, INotebookFutureDone
|
IModelViewWizardDetails, IModelViewWizardPageDetails, INotebookManagerDetails, INotebookSessionDetails, INotebookKernelDetails, INotebookFutureDetails, FutureMessageType, INotebookFutureDone, ISingleNotebookEditOperation
|
||||||
} from 'sql/workbench/api/common/sqlExtHostTypes';
|
} from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
import { EditorViewColumn } from 'vs/workbench/api/shared/editor';
|
||||||
|
import { IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol';
|
||||||
|
|
||||||
export abstract class ExtHostAccountManagementShape {
|
export abstract class ExtHostAccountManagementShape {
|
||||||
$autoOAuthCancelled(handle: number): Thenable<void> { throw ni(); }
|
$autoOAuthCancelled(handle: number): Thenable<void> { throw ni(); }
|
||||||
@@ -572,7 +574,9 @@ export const SqlMainContext = {
|
|||||||
MainThreadDashboard: createMainId<MainThreadDashboardShape>('MainThreadDashboard'),
|
MainThreadDashboard: createMainId<MainThreadDashboardShape>('MainThreadDashboard'),
|
||||||
MainThreadModelViewDialog: createMainId<MainThreadModelViewDialogShape>('MainThreadModelViewDialog'),
|
MainThreadModelViewDialog: createMainId<MainThreadModelViewDialogShape>('MainThreadModelViewDialog'),
|
||||||
MainThreadQueryEditor: createMainId<MainThreadQueryEditorShape>('MainThreadQueryEditor'),
|
MainThreadQueryEditor: createMainId<MainThreadQueryEditorShape>('MainThreadQueryEditor'),
|
||||||
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook')
|
MainThreadNotebook: createMainId<MainThreadNotebookShape>('MainThreadNotebook'),
|
||||||
|
MainThreadNotebookDocumentsAndEditors: createMainId<MainThreadNotebookDocumentsAndEditorsShape>('MainThreadNotebookDocumentsAndEditors')
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SqlExtHostContext = {
|
export const SqlExtHostContext = {
|
||||||
@@ -592,7 +596,8 @@ export const SqlExtHostContext = {
|
|||||||
ExtHostDashboard: createExtId<ExtHostDashboardShape>('ExtHostDashboard'),
|
ExtHostDashboard: createExtId<ExtHostDashboardShape>('ExtHostDashboard'),
|
||||||
ExtHostModelViewDialog: createExtId<ExtHostModelViewDialogShape>('ExtHostModelViewDialog'),
|
ExtHostModelViewDialog: createExtId<ExtHostModelViewDialogShape>('ExtHostModelViewDialog'),
|
||||||
ExtHostQueryEditor: createExtId<ExtHostQueryEditorShape>('ExtHostQueryEditor'),
|
ExtHostQueryEditor: createExtId<ExtHostQueryEditorShape>('ExtHostQueryEditor'),
|
||||||
ExtHostNotebook: createExtId<ExtHostNotebookShape>('ExtHostNotebook')
|
ExtHostNotebook: createExtId<ExtHostNotebookShape>('ExtHostNotebook'),
|
||||||
|
ExtHostNotebookDocumentsAndEditors: createExtId<ExtHostNotebookDocumentsAndEditorsShape>('ExtHostNotebookDocumentsAndEditors')
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface MainThreadDashboardShape extends IDisposable {
|
export interface MainThreadDashboardShape extends IDisposable {
|
||||||
@@ -712,7 +717,7 @@ export interface ExtHostModelViewDialogShape {
|
|||||||
|
|
||||||
export interface MainThreadModelViewDialogShape extends IDisposable {
|
export interface MainThreadModelViewDialogShape extends IDisposable {
|
||||||
$openEditor(handle: number, modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void>;
|
$openEditor(handle: number, modelViewId: string, title: string, options?: sqlops.ModelViewEditorOptions, position?: vscode.ViewColumn): Thenable<void>;
|
||||||
$openDialog(handle: number): Thenable<void>;
|
$openDialog(handle: number, dialogName?: string): Thenable<void>;
|
||||||
$closeDialog(handle: number): Thenable<void>;
|
$closeDialog(handle: number): Thenable<void>;
|
||||||
$setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable<void>;
|
$setDialogDetails(handle: number, details: IModelViewDialogDetails): Thenable<void>;
|
||||||
$setTabDetails(handle: number, details: IModelViewTabDetails): Thenable<void>;
|
$setTabDetails(handle: number, details: IModelViewTabDetails): Thenable<void>;
|
||||||
@@ -750,8 +755,8 @@ export interface ExtHostNotebookShape {
|
|||||||
$doStopServer(managerHandle: number): Thenable<void>;
|
$doStopServer(managerHandle: number): Thenable<void>;
|
||||||
|
|
||||||
// Content Manager APIs
|
// Content Manager APIs
|
||||||
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebook>;
|
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebookContents>;
|
||||||
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook>;
|
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebookContents): Thenable<sqlops.nb.INotebookContents>;
|
||||||
|
|
||||||
// Session Manager APIs
|
// Session Manager APIs
|
||||||
$refreshSpecs(managerHandle: number): Thenable<sqlops.nb.IAllKernels>;
|
$refreshSpecs(managerHandle: number): Thenable<sqlops.nb.IAllKernels>;
|
||||||
@@ -780,3 +785,40 @@ export interface MainThreadNotebookShape extends IDisposable {
|
|||||||
$onFutureDone(futureId: number, done: INotebookFutureDone): void;
|
$onFutureDone(futureId: number, done: INotebookFutureDone): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface INotebookDocumentsAndEditorsDelta {
|
||||||
|
removedDocuments?: UriComponents[];
|
||||||
|
addedDocuments?: INotebookModelAddedData[];
|
||||||
|
removedEditors?: string[];
|
||||||
|
addedEditors?: INotebookEditorAddData[];
|
||||||
|
newActiveEditor?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotebookModelAddedData {
|
||||||
|
uri: UriComponents;
|
||||||
|
providerId: string;
|
||||||
|
isDirty: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotebookEditorAddData {
|
||||||
|
id: string;
|
||||||
|
documentUri: UriComponents;
|
||||||
|
editorPosition: EditorViewColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INotebookShowOptions {
|
||||||
|
position?: EditorViewColumn;
|
||||||
|
preserveFocus?: boolean;
|
||||||
|
preview?: boolean;
|
||||||
|
providerId?: string;
|
||||||
|
connectionId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExtHostNotebookDocumentsAndEditorsShape {
|
||||||
|
$acceptDocumentsAndEditorsDelta(delta: INotebookDocumentsAndEditorsDelta): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable {
|
||||||
|
$trySaveDocument(uri: UriComponents): Thenable<boolean>;
|
||||||
|
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): TPromise<string>;
|
||||||
|
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): TPromise<boolean>;
|
||||||
|
}
|
||||||
@@ -881,4 +881,45 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||||||
assert.equal(profileWithCredentials.userName, username);
|
assert.equal(profileWithCredentials.userName, username);
|
||||||
assert.equal(profileWithCredentials.options['azureAccountToken'], testToken);
|
assert.equal(profileWithCredentials.options['azureAccountToken'], testToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('addSavedPassword fills in Azure access token for selected tenant', async () => {
|
||||||
|
// Set up a connection profile that uses Azure
|
||||||
|
let azureConnectionProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile);
|
||||||
|
azureConnectionProfile.authenticationType = 'AzureMFA';
|
||||||
|
let username = 'testuser@microsoft.com';
|
||||||
|
azureConnectionProfile.userName = username;
|
||||||
|
let servername = 'test-database.database.windows.net';
|
||||||
|
azureConnectionProfile.serverName = servername;
|
||||||
|
let azureTenantId = 'testTenant';
|
||||||
|
azureConnectionProfile.azureTenantId = azureTenantId;
|
||||||
|
|
||||||
|
// Set up the account management service to return a token for the given user
|
||||||
|
accountManagementService.setup(x => x.getAccountsForProvider(TypeMoq.It.isAny())).returns(providerId => Promise.resolve<sqlops.Account[]>([
|
||||||
|
{
|
||||||
|
key: {
|
||||||
|
accountId: username,
|
||||||
|
providerId: providerId
|
||||||
|
},
|
||||||
|
displayInfo: undefined,
|
||||||
|
isStale: false,
|
||||||
|
properties: undefined
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
let testToken = 'testToken';
|
||||||
|
let returnedTokens = {};
|
||||||
|
returnedTokens['azurePublicCloud'] = { token: 'badToken' };
|
||||||
|
returnedTokens[azureTenantId] = { token: testToken };
|
||||||
|
accountManagementService.setup(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(returnedTokens));
|
||||||
|
connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is(profile => profile.authenticationType === 'AzureMFA'))).returns(profile => Promise.resolve({
|
||||||
|
profile: profile,
|
||||||
|
savedCred: false
|
||||||
|
}));
|
||||||
|
|
||||||
|
// If I call addSavedPassword
|
||||||
|
let profileWithCredentials = await connectionManagementService.addSavedPassword(azureConnectionProfile);
|
||||||
|
|
||||||
|
// Then the returned profile has the account token set corresponding to the requested tenant
|
||||||
|
assert.equal(profileWithCredentials.userName, username);
|
||||||
|
assert.equal(profileWithCredentials.options['azureAccountToken'], testToken);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
99
src/sqltest/parts/notebook/common.ts
Normal file
99
src/sqltest/parts/notebook/common.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { nb, IConnectionProfile } from 'sqlops';
|
||||||
|
|
||||||
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
|
import { INotebookModel, ICellModel, IClientSession, IDefaultConnection } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
|
import { NotebookChangeType, CellType } from 'sql/parts/notebook/models/contracts';
|
||||||
|
import { INotebookManager } from 'sql/services/notebook/notebookService';
|
||||||
|
import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
|
||||||
|
export class NotebookModelStub implements INotebookModel {
|
||||||
|
constructor(private _languageInfo?: nb.ILanguageInfo) {
|
||||||
|
}
|
||||||
|
public trustedMode: boolean;
|
||||||
|
|
||||||
|
public get languageInfo(): nb.ILanguageInfo {
|
||||||
|
return this._languageInfo;
|
||||||
|
}
|
||||||
|
onCellChange(cell: ICellModel, change: NotebookChangeType): void {
|
||||||
|
// Default: do nothing
|
||||||
|
}
|
||||||
|
get cells(): ReadonlyArray<ICellModel> {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
get clientSession(): IClientSession {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
get notebookManager(): INotebookManager {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
get kernelsChanged(): Event<nb.IKernelSpec> {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
} get defaultKernel(): nb.IKernelSpec {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
get contextsChanged(): Event<void> {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
get specs(): nb.IAllKernels {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
get contexts(): IDefaultConnection {
|
||||||
|
throw new Error('method not implemented.');
|
||||||
|
}
|
||||||
|
changeKernel(displayName: string): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
changeContext(host: string, connection?: IConnectionProfile): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
findCellIndex(cellModel: ICellModel): number {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
addCell(cellType: CellType, index?: number): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
deleteCell(cellModel: ICellModel): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
saveModel(): Promise<boolean> {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
pushEditOperations(edits: ISingleNotebookEditOperation[]): void {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NotebookManagerStub implements INotebookManager {
|
||||||
|
providerId: string;
|
||||||
|
contentManager: nb.ContentManager;
|
||||||
|
sessionManager: nb.SessionManager;
|
||||||
|
serverManager: nb.ServerManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServerManagerStub implements nb.ServerManager {
|
||||||
|
public onServerStartedEmitter = new Emitter<void>();
|
||||||
|
onServerStarted: Event<void> = this.onServerStartedEmitter.event;
|
||||||
|
isStarted: boolean = false;
|
||||||
|
calledStart: boolean = false;
|
||||||
|
calledEnd: boolean = false;
|
||||||
|
public result: Promise<void> = undefined;
|
||||||
|
|
||||||
|
startServer(): Promise<void> {
|
||||||
|
this.calledStart = true;
|
||||||
|
return this.result;
|
||||||
|
}
|
||||||
|
stopServer(): Promise<void> {
|
||||||
|
this.calledEnd = true;
|
||||||
|
return this.result;
|
||||||
|
}
|
||||||
|
}
|
||||||
262
src/sqltest/parts/notebook/model/cell.test.ts
Normal file
262
src/sqltest/parts/notebook/model/cell.test.ts
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import { nb } from 'sqlops';
|
||||||
|
|
||||||
|
import * as objects from 'vs/base/common/objects';
|
||||||
|
|
||||||
|
import { CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||||
|
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||||
|
import { NotebookModelStub } from '../common';
|
||||||
|
import { EmptyFuture } from 'sql/services/notebook/sessionManager';
|
||||||
|
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
|
|
||||||
|
describe('Cell Model', function (): void {
|
||||||
|
let factory = new ModelFactory();
|
||||||
|
it('Should set default values if none defined', async function (): Promise<void> {
|
||||||
|
let cell = factory.createCell(undefined, undefined);
|
||||||
|
should(cell.cellType).equal(CellTypes.Code);
|
||||||
|
should(cell.source).equal('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should update values', async function (): Promise<void> {
|
||||||
|
let cell = factory.createCell(undefined, undefined);
|
||||||
|
cell.language = 'sql';
|
||||||
|
should(cell.language).equal('sql');
|
||||||
|
cell.source = 'abcd';
|
||||||
|
should(cell.source).equal('abcd');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should match ICell values if defined', async function (): Promise<void> {
|
||||||
|
let output: nb.IStreamResult = {
|
||||||
|
output_type: 'stream',
|
||||||
|
text: 'Some output',
|
||||||
|
name: 'stdout'
|
||||||
|
};
|
||||||
|
let cellData: nb.ICellContents = {
|
||||||
|
cell_type: CellTypes.Markdown,
|
||||||
|
source: 'some *markdown*',
|
||||||
|
outputs: [output],
|
||||||
|
metadata: { language: 'python'},
|
||||||
|
execution_count: 1
|
||||||
|
};
|
||||||
|
let cell = factory.createCell(cellData, undefined);
|
||||||
|
should(cell.cellType).equal(cellData.cell_type);
|
||||||
|
should(cell.source).equal(cellData.source);
|
||||||
|
should(cell.outputs).have.length(1);
|
||||||
|
should(cell.outputs[0].output_type).equal('stream');
|
||||||
|
should((<nb.IStreamResult>cell.outputs[0]).text).equal('Some output');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('Should set cell language to python if defined as python in languageInfo', async function (): Promise<void> {
|
||||||
|
let cellData: nb.ICellContents = {
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'print(\'1\')',
|
||||||
|
metadata: { language: 'python'},
|
||||||
|
execution_count: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let notebookModel = new NotebookModelStub({
|
||||||
|
name: 'python',
|
||||||
|
version: '',
|
||||||
|
mimetype: ''
|
||||||
|
});
|
||||||
|
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||||
|
should(cell.language).equal('python');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set cell language to python if defined as pyspark in languageInfo', async function (): Promise<void> {
|
||||||
|
let cellData: nb.ICellContents = {
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'print(\'1\')',
|
||||||
|
metadata: { language: 'python'},
|
||||||
|
execution_count: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let notebookModel = new NotebookModelStub({
|
||||||
|
name: 'pyspark',
|
||||||
|
version: '',
|
||||||
|
mimetype: ''
|
||||||
|
});
|
||||||
|
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||||
|
should(cell.language).equal('python');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set cell language to scala if defined as scala in languageInfo', async function (): Promise<void> {
|
||||||
|
let cellData: nb.ICellContents = {
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'print(\'1\')',
|
||||||
|
metadata: { language: 'python'},
|
||||||
|
execution_count: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let notebookModel = new NotebookModelStub({
|
||||||
|
name: 'scala',
|
||||||
|
version: '',
|
||||||
|
mimetype: ''
|
||||||
|
});
|
||||||
|
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||||
|
should(cell.language).equal('scala');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set cell language to python if no language defined', async function (): Promise<void> {
|
||||||
|
let cellData: nb.ICellContents = {
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'print(\'1\')',
|
||||||
|
metadata: { language: 'python'},
|
||||||
|
execution_count: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let notebookModel = new NotebookModelStub({
|
||||||
|
name: '',
|
||||||
|
version: '',
|
||||||
|
mimetype: ''
|
||||||
|
});
|
||||||
|
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||||
|
should(cell.language).equal('python');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should match cell language to language specified if unknown language defined in languageInfo', async function (): Promise<void> {
|
||||||
|
let cellData: nb.ICellContents = {
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'std::cout << "hello world";',
|
||||||
|
metadata: { language: 'python'},
|
||||||
|
execution_count: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let notebookModel = new NotebookModelStub({
|
||||||
|
name: 'cplusplus',
|
||||||
|
version: '',
|
||||||
|
mimetype: ''
|
||||||
|
});
|
||||||
|
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||||
|
should(cell.language).equal('cplusplus');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should match cell language to mimetype name is not supplied in languageInfo', async function (): Promise<void> {
|
||||||
|
let cellData: nb.ICellContents = {
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'print(\'1\')',
|
||||||
|
metadata: { language: 'python'},
|
||||||
|
execution_count: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
let notebookModel = new NotebookModelStub({
|
||||||
|
name: '',
|
||||||
|
version: '',
|
||||||
|
mimetype: 'x-scala'
|
||||||
|
});
|
||||||
|
let cell = factory.createCell(cellData, { notebook: notebookModel, isTrusted: false });
|
||||||
|
should(cell.language).equal('scala');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Model Future handling', function(): void {
|
||||||
|
let future: TypeMoq.Mock<EmptyFuture>;
|
||||||
|
let cell: ICellModel;
|
||||||
|
beforeEach(() => {
|
||||||
|
future = TypeMoq.Mock.ofType(EmptyFuture);
|
||||||
|
cell = factory.createCell({
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'print "Hello"',
|
||||||
|
metadata: { language: 'python'},
|
||||||
|
execution_count: 1
|
||||||
|
}, {
|
||||||
|
notebook: new NotebookModelStub({
|
||||||
|
name: '',
|
||||||
|
version: '',
|
||||||
|
mimetype: 'x-scala'
|
||||||
|
}),
|
||||||
|
isTrusted: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send and handle incoming messages', async () => {
|
||||||
|
// Given a future
|
||||||
|
let onReply: nb.MessageHandler<nb.IShellMessage>;
|
||||||
|
let onIopub: nb.MessageHandler<nb.IIOPubMessage>;
|
||||||
|
future.setup(f => f.setReplyHandler(TypeMoq.It.isAny())).callback((handler) => onReply = handler);
|
||||||
|
future.setup(f => f.setIOPubHandler(TypeMoq.It.isAny())).callback((handler) => onIopub = handler);
|
||||||
|
let outputs: ReadonlyArray<nb.ICellOutput> = undefined;
|
||||||
|
cell.onOutputsChanged((o => outputs = o));
|
||||||
|
|
||||||
|
// When I set it on the cell
|
||||||
|
cell.setFuture(future.object);
|
||||||
|
|
||||||
|
// Then I expect outputs to have been cleared
|
||||||
|
should(outputs).have.length(0);
|
||||||
|
should(onReply).not.be.undefined();
|
||||||
|
// ... And when I send an IoPub message
|
||||||
|
let message: nb.IIOPubMessage = {
|
||||||
|
channel: 'iopub',
|
||||||
|
type: 'iopub',
|
||||||
|
parent_header: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
header: <nb.IHeader> {
|
||||||
|
msg_type: 'stream'
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
text: 'Printed hello world'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onIopub.handle(message);
|
||||||
|
// Then I expect an output to be added
|
||||||
|
should(outputs).have.length(1);
|
||||||
|
should(outputs[0].output_type).equal('stream');
|
||||||
|
|
||||||
|
message = objects.deepClone(message);
|
||||||
|
message.header.msg_type = 'display_data';
|
||||||
|
onIopub.handle(message);
|
||||||
|
should(outputs[1].output_type).equal('display_data');
|
||||||
|
|
||||||
|
// ... TODO: And when I sent a reply I expect it to be processed.
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete transient tag while handling incoming messages', async () => {
|
||||||
|
// Given a future
|
||||||
|
let onIopub: nb.MessageHandler<nb.IIOPubMessage>;
|
||||||
|
future.setup(f => f.setIOPubHandler(TypeMoq.It.isAny())).callback((handler) => onIopub = handler);
|
||||||
|
let outputs: ReadonlyArray<nb.ICellOutput> = undefined;
|
||||||
|
cell.onOutputsChanged((o => outputs = o));
|
||||||
|
|
||||||
|
//Set the future
|
||||||
|
cell.setFuture(future.object);
|
||||||
|
|
||||||
|
// ... And when I send an IoPub message
|
||||||
|
let message: nb.IIOPubMessage = {
|
||||||
|
channel: 'iopub',
|
||||||
|
type: 'iopub',
|
||||||
|
parent_header: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
header: <nb.IHeader> {
|
||||||
|
msg_type: 'display_data'
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
text: 'Printed hello world',
|
||||||
|
transient: 'transient data'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
onIopub.handle(message);
|
||||||
|
//Output array's length should be 1
|
||||||
|
//'transient' tag should no longer exist in the output
|
||||||
|
should(outputs).have.length(1);
|
||||||
|
should(outputs[0]['transient']).be.undefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should dispose old future', async () => {
|
||||||
|
let oldFuture = TypeMoq.Mock.ofType(EmptyFuture);
|
||||||
|
cell.setFuture(oldFuture.object);
|
||||||
|
|
||||||
|
cell.setFuture(future.object);
|
||||||
|
|
||||||
|
oldFuture.verify(f => f.dispose(), TypeMoq.Times.once());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
202
src/sqltest/parts/notebook/model/clientSession.test.ts
Normal file
202
src/sqltest/parts/notebook/model/clientSession.test.ts
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import { nb } from 'sqlops';
|
||||||
|
|
||||||
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
|
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||||
|
import URI from 'vs/base/common/uri';
|
||||||
|
|
||||||
|
import { ClientSession } from 'sql/parts/notebook/models/clientSession';
|
||||||
|
import { SessionManager, EmptySession } from 'sql/services/notebook/sessionManager';
|
||||||
|
import { NotebookManagerStub, ServerManagerStub } from 'sqltest/parts/notebook/common';
|
||||||
|
|
||||||
|
describe('Client Session', function(): void {
|
||||||
|
let path = URI.file('my/notebook.ipynb');
|
||||||
|
let notebookManager: NotebookManagerStub;
|
||||||
|
let serverManager: ServerManagerStub;
|
||||||
|
let mockSessionManager: TypeMoq.Mock<nb.SessionManager>;
|
||||||
|
let notificationService: TypeMoq.Mock<INotificationService>;
|
||||||
|
let session: ClientSession;
|
||||||
|
let remoteSession: ClientSession;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
serverManager = new ServerManagerStub();
|
||||||
|
mockSessionManager = TypeMoq.Mock.ofType(SessionManager);
|
||||||
|
notebookManager = new NotebookManagerStub();
|
||||||
|
notebookManager.serverManager = serverManager;
|
||||||
|
notebookManager.sessionManager = mockSessionManager.object;
|
||||||
|
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
|
||||||
|
|
||||||
|
session = new ClientSession({
|
||||||
|
notebookManager: notebookManager,
|
||||||
|
notebookUri: path,
|
||||||
|
notificationService: notificationService.object
|
||||||
|
});
|
||||||
|
|
||||||
|
let serverlessNotebookManager = new NotebookManagerStub();
|
||||||
|
serverlessNotebookManager.sessionManager = mockSessionManager.object;
|
||||||
|
remoteSession = new ClientSession({
|
||||||
|
notebookManager: serverlessNotebookManager,
|
||||||
|
notebookUri: path,
|
||||||
|
notificationService: notificationService.object
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should set path, isReady and ready on construction', function(): void {
|
||||||
|
should(session.notebookUri).equal(path);
|
||||||
|
should(session.ready).not.be.undefined();
|
||||||
|
should(session.isReady).be.false();
|
||||||
|
should(session.status).equal('starting');
|
||||||
|
should(session.isInErrorState).be.false();
|
||||||
|
should(session.errorMessage).be.undefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should call on serverManager startup if set', async function(): Promise<void> {
|
||||||
|
// Given I have a serverManager that starts successfully
|
||||||
|
serverManager.result = Promise.resolve();
|
||||||
|
should(session.isReady).be.false();
|
||||||
|
|
||||||
|
// When I kick off initialization
|
||||||
|
await session.initialize();
|
||||||
|
|
||||||
|
// Then I expect ready to be completed too
|
||||||
|
await session.ready;
|
||||||
|
should(serverManager.calledStart).be.true();
|
||||||
|
should(session.isReady).be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should go to error state if serverManager startup fails', async function(): Promise<void> {
|
||||||
|
// Given I have a serverManager that fails to start
|
||||||
|
serverManager.result = Promise.reject('error');
|
||||||
|
should(session.isInErrorState).be.false();
|
||||||
|
|
||||||
|
// When I initialize
|
||||||
|
await session.initialize();
|
||||||
|
|
||||||
|
// Then I expect ready to complete, but isInErrorState to be true
|
||||||
|
await session.ready;
|
||||||
|
should(session.isReady).be.true();
|
||||||
|
should(serverManager.calledStart).be.true();
|
||||||
|
should(session.isInErrorState).be.true();
|
||||||
|
should(session.errorMessage).equal('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be ready when session manager is ready', async function(): Promise<void> {
|
||||||
|
serverManager.result = new Promise((resolve) => {
|
||||||
|
serverManager.isStarted = true;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
mockSessionManager.setup(s => s.ready).returns(() => Promise.resolve());
|
||||||
|
|
||||||
|
// When I call initialize
|
||||||
|
await session.initialize();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
should(session.isReady).be.true();
|
||||||
|
should(session.isInErrorState).be.false();
|
||||||
|
await session.ready;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should be in error state if server fails to start', async function(): Promise<void> {
|
||||||
|
serverManager.result = new Promise((resolve) => {
|
||||||
|
serverManager.isStarted = false;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
mockSessionManager.setup(s => s.ready).returns(() => Promise.resolve());
|
||||||
|
|
||||||
|
// When I call initialize
|
||||||
|
await session.initialize();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
await session.ready;
|
||||||
|
should(session.isReady).be.true();
|
||||||
|
should(session.isInErrorState).be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should go to error state if sessionManager fails', async function(): Promise<void> {
|
||||||
|
serverManager.isStarted = true;
|
||||||
|
mockSessionManager.setup(s => s.isReady).returns(() => false);
|
||||||
|
mockSessionManager.setup(s => s.ready).returns(() => Promise.reject('error'));
|
||||||
|
|
||||||
|
// When I call initialize
|
||||||
|
await session.initialize();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
should(session.isReady).be.true();
|
||||||
|
should(session.isInErrorState).be.true();
|
||||||
|
should(session.errorMessage).equal('error');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should start session automatically if kernel preference requests it', async function(): Promise<void> {
|
||||||
|
serverManager.isStarted = true;
|
||||||
|
mockSessionManager.setup(s => s.ready).returns(() => Promise.resolve());
|
||||||
|
let sessionMock = TypeMoq.Mock.ofType(EmptySession);
|
||||||
|
let startOptions: nb.ISessionOptions = undefined;
|
||||||
|
mockSessionManager.setup(s => s.startNew(TypeMoq.It.isAny())).returns((options) => {
|
||||||
|
startOptions = options;
|
||||||
|
return Promise.resolve(sessionMock.object);
|
||||||
|
});
|
||||||
|
|
||||||
|
// When I call initialize after defining kernel preferences
|
||||||
|
session.kernelPreference = {
|
||||||
|
shouldStart: true,
|
||||||
|
name: 'python'
|
||||||
|
};
|
||||||
|
await session.initialize();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
should(session.isReady).be.true();
|
||||||
|
should(session.isInErrorState).be.false();
|
||||||
|
should(startOptions.kernelName).equal('python');
|
||||||
|
should(startOptions.path).equal(path.fsPath);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should shutdown session even if no serverManager is set', async function(): Promise<void> {
|
||||||
|
// Given a session against a remote server
|
||||||
|
let expectedId = 'abc';
|
||||||
|
mockSessionManager.setup(s => s.isReady).returns(() => true);
|
||||||
|
mockSessionManager.setup(s => s.shutdown(TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||||
|
let sessionMock = TypeMoq.Mock.ofType(EmptySession);
|
||||||
|
sessionMock.setup(s => s.id).returns(() => expectedId);
|
||||||
|
mockSessionManager.setup(s => s.startNew(TypeMoq.It.isAny())).returns(() => Promise.resolve(sessionMock.object));
|
||||||
|
|
||||||
|
remoteSession.kernelPreference = {
|
||||||
|
shouldStart: true,
|
||||||
|
name: 'python'
|
||||||
|
};
|
||||||
|
await remoteSession.initialize();
|
||||||
|
|
||||||
|
// When I call shutdown
|
||||||
|
await remoteSession.shutdown();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
mockSessionManager.verify(s => s.shutdown(TypeMoq.It.isValue(expectedId)), TypeMoq.Times.once());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('Should stop server if server is set', async function(): Promise<void> {
|
||||||
|
// Given a kernel has been started
|
||||||
|
serverManager.isStarted = true;
|
||||||
|
serverManager.result = Promise.resolve();
|
||||||
|
mockSessionManager.setup(s => s.isReady).returns(() => true);
|
||||||
|
mockSessionManager.setup(s => s.shutdown(TypeMoq.It.isAny())).returns(() => Promise.resolve());
|
||||||
|
|
||||||
|
await session.initialize();
|
||||||
|
|
||||||
|
// When I call shutdown
|
||||||
|
await session.shutdown();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
should(serverManager.calledEnd).be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
77
src/sqltest/parts/notebook/model/contentManagers.test.ts
Normal file
77
src/sqltest/parts/notebook/model/contentManagers.test.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { nb } from 'sqlops';
|
||||||
|
|
||||||
|
import URI from 'vs/base/common/uri';
|
||||||
|
import * as tempWrite from 'temp-write';
|
||||||
|
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
|
||||||
|
import * as testUtils from '../../../utils/testUtils';
|
||||||
|
import { CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||||
|
|
||||||
|
let expectedNotebookContent: nb.INotebookContents = {
|
||||||
|
cells: [{
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'insert into t1 values (c1, c2)',
|
||||||
|
metadata: { language: 'python' },
|
||||||
|
execution_count: 1
|
||||||
|
}],
|
||||||
|
metadata: {
|
||||||
|
kernelspec: {
|
||||||
|
name: 'mssql',
|
||||||
|
language: 'sql'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nbformat: 5,
|
||||||
|
nbformat_minor: 0
|
||||||
|
};
|
||||||
|
let notebookContentString = JSON.stringify(expectedNotebookContent);
|
||||||
|
|
||||||
|
function verifyMatchesExpectedNotebook(notebook: nb.INotebookContents): void {
|
||||||
|
should(notebook.cells).have.length(1, 'Expected 1 cell');
|
||||||
|
should(notebook.cells[0].cell_type).equal(CellTypes.Code);
|
||||||
|
should(notebook.cells[0].source).equal(expectedNotebookContent.cells[0].source);
|
||||||
|
should(notebook.metadata.kernelspec.name).equal(expectedNotebookContent.metadata.kernelspec.name);
|
||||||
|
should(notebook.nbformat).equal(expectedNotebookContent.nbformat);
|
||||||
|
should(notebook.nbformat_minor).equal(expectedNotebookContent.nbformat_minor);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Local Content Manager', function(): void {
|
||||||
|
let contentManager = new LocalContentManager();
|
||||||
|
|
||||||
|
it('Should return undefined if path is undefined', async function(): Promise<void> {
|
||||||
|
let content = await contentManager.getNotebookContents(undefined);
|
||||||
|
should(content).be.undefined();
|
||||||
|
// tslint:disable-next-line:no-null-keyword
|
||||||
|
content = await contentManager.getNotebookContents(null);
|
||||||
|
should(content).be.undefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw if file does not exist', async function(): Promise<void> {
|
||||||
|
await testUtils.assertThrowsAsync(async () => await contentManager.getNotebookContents(URI.file('/path/doesnot/exist.ipynb')), undefined);
|
||||||
|
});
|
||||||
|
it('Should return notebook contents parsed as INotebook when valid notebook file parsed', async function(): Promise<void> {
|
||||||
|
// Given a file containing a valid notebook
|
||||||
|
let localFile = tempWrite.sync(notebookContentString, 'notebook.ipynb');
|
||||||
|
// when I read the content
|
||||||
|
let notebook = await contentManager.getNotebookContents(URI.file(localFile));
|
||||||
|
// then I expect notebook format to match
|
||||||
|
verifyMatchesExpectedNotebook(notebook);
|
||||||
|
});
|
||||||
|
it('Should ignore invalid content in the notebook file', async function(): Promise<void> {
|
||||||
|
// Given a file containing a notebook with some garbage properties
|
||||||
|
let invalidContent = notebookContentString + '\\nasddfdsafasdf';
|
||||||
|
let localFile = tempWrite.sync(invalidContent, 'notebook.ipynb');
|
||||||
|
// when I read the content
|
||||||
|
let notebook = await contentManager.getNotebookContents(URI.file(localFile));
|
||||||
|
// then I expect notebook format to still be valid
|
||||||
|
verifyMatchesExpectedNotebook(notebook);
|
||||||
|
});
|
||||||
|
});
|
||||||
251
src/sqltest/parts/notebook/model/notebookModel.test.ts
Normal file
251
src/sqltest/parts/notebook/model/notebookModel.test.ts
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as should from 'should';
|
||||||
|
import * as TypeMoq from 'typemoq';
|
||||||
|
import { nb } from 'sqlops';
|
||||||
|
|
||||||
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
|
import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService';
|
||||||
|
import URI from 'vs/base/common/uri';
|
||||||
|
|
||||||
|
import { LocalContentManager } from 'sql/services/notebook/localContentManager';
|
||||||
|
import * as testUtils from '../../../utils/testUtils';
|
||||||
|
import { NotebookManagerStub } from '../common';
|
||||||
|
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||||
|
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||||
|
import { IClientSession, ICellModel, INotebookModelOptions } from 'sql/parts/notebook/models/modelInterfaces';
|
||||||
|
import { ClientSession } from 'sql/parts/notebook/models/clientSession';
|
||||||
|
import { CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||||
|
import { Deferred } from 'sql/base/common/promise';
|
||||||
|
import { ConnectionManagementService } from 'sql/parts/connection/common/connectionManagementService';
|
||||||
|
import { Memento } from 'vs/workbench/common/memento';
|
||||||
|
import { Emitter } from 'vs/base/common/event';
|
||||||
|
|
||||||
|
let expectedNotebookContent: nb.INotebookContents = {
|
||||||
|
cells: [{
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'insert into t1 values (c1, c2)',
|
||||||
|
metadata: { language: 'python' },
|
||||||
|
execution_count: 1
|
||||||
|
}, {
|
||||||
|
cell_type: CellTypes.Markdown,
|
||||||
|
source: 'I am *markdown*',
|
||||||
|
metadata: { language: 'python' },
|
||||||
|
execution_count: 1
|
||||||
|
}],
|
||||||
|
metadata: {
|
||||||
|
kernelspec: {
|
||||||
|
name: 'mssql',
|
||||||
|
language: 'sql'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nbformat: 5,
|
||||||
|
nbformat_minor: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
let expectedNotebookContentOneCell: nb.INotebookContents = {
|
||||||
|
cells: [{
|
||||||
|
cell_type: CellTypes.Code,
|
||||||
|
source: 'insert into t1 values (c1, c2)',
|
||||||
|
metadata: { language: 'python' },
|
||||||
|
execution_count: 1
|
||||||
|
}],
|
||||||
|
metadata: {
|
||||||
|
kernelspec: {
|
||||||
|
name: 'mssql',
|
||||||
|
language: 'sql'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nbformat: 5,
|
||||||
|
nbformat_minor: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
let defaultUri = URI.file('/some/path.ipynb');
|
||||||
|
|
||||||
|
let mockClientSession: TypeMoq.Mock<IClientSession>;
|
||||||
|
let sessionReady: Deferred<void>;
|
||||||
|
let mockModelFactory: TypeMoq.Mock<ModelFactory>;
|
||||||
|
let notificationService: TypeMoq.Mock<INotificationService>;
|
||||||
|
|
||||||
|
describe('notebook model', function(): void {
|
||||||
|
let notebookManager = new NotebookManagerStub();
|
||||||
|
let memento: TypeMoq.Mock<Memento>;
|
||||||
|
let queryConnectionService: TypeMoq.Mock<ConnectionManagementService>;
|
||||||
|
let defaultModelOptions: INotebookModelOptions;
|
||||||
|
beforeEach(() => {
|
||||||
|
sessionReady = new Deferred<void>();
|
||||||
|
notificationService = TypeMoq.Mock.ofType(TestNotificationService, TypeMoq.MockBehavior.Loose);
|
||||||
|
memento = TypeMoq.Mock.ofType(Memento, TypeMoq.MockBehavior.Loose, '');
|
||||||
|
memento.setup(x => x.getMemento(TypeMoq.It.isAny())).returns(() => void 0);
|
||||||
|
queryConnectionService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Loose, memento.object, undefined);
|
||||||
|
queryConnectionService.callBase = true;
|
||||||
|
defaultModelOptions = {
|
||||||
|
notebookUri: defaultUri,
|
||||||
|
factory: new ModelFactory(),
|
||||||
|
notebookManager,
|
||||||
|
notificationService: notificationService.object,
|
||||||
|
connectionService: queryConnectionService.object };
|
||||||
|
mockClientSession = TypeMoq.Mock.ofType(ClientSession, undefined, defaultModelOptions);
|
||||||
|
mockClientSession.setup(c => c.initialize(TypeMoq.It.isAny())).returns(() => {
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
mockClientSession.setup(c => c.ready).returns(() => sessionReady.promise);
|
||||||
|
mockModelFactory = TypeMoq.Mock.ofType(ModelFactory);
|
||||||
|
mockModelFactory.callBase = true;
|
||||||
|
mockModelFactory.setup(f => f.createClientSession(TypeMoq.It.isAny())).returns(() => {
|
||||||
|
return mockClientSession.object;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should create single cell if model has no contents', async function(): Promise<void> {
|
||||||
|
// Given an empty notebook
|
||||||
|
let emptyNotebook: nb.INotebookContents = {
|
||||||
|
cells: [],
|
||||||
|
metadata: {
|
||||||
|
kernelspec: {
|
||||||
|
name: 'mssql',
|
||||||
|
language: 'sql'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
nbformat: 5,
|
||||||
|
nbformat_minor: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(emptyNotebook));
|
||||||
|
notebookManager.contentManager = mockContentManager.object;
|
||||||
|
|
||||||
|
// When I initialize the model
|
||||||
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
|
await model.requestModelLoad();
|
||||||
|
|
||||||
|
// Then I expect to have 1 code cell as the contents
|
||||||
|
should(model.cells).have.length(1);
|
||||||
|
should(model.cells[0].source).be.empty();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should throw if model load fails', async function(): Promise<void> {
|
||||||
|
// Given a call to get Contents fails
|
||||||
|
let error = new Error('File not found');
|
||||||
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).throws(error);
|
||||||
|
notebookManager.contentManager = mockContentManager.object;
|
||||||
|
|
||||||
|
// When I initalize the model
|
||||||
|
// Then it should throw
|
||||||
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
|
should(model.inErrorState).be.false();
|
||||||
|
await testUtils.assertThrowsAsync(() => model.requestModelLoad(), error.message);
|
||||||
|
should(model.inErrorState).be.true();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should convert cell info to CellModels', async function(): Promise<void> {
|
||||||
|
// Given a notebook with 2 cells
|
||||||
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContent));
|
||||||
|
notebookManager.contentManager = mockContentManager.object;
|
||||||
|
|
||||||
|
// When I initalize the model
|
||||||
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
|
await model.requestModelLoad();
|
||||||
|
|
||||||
|
// Then I expect all cells to be in the model
|
||||||
|
should(model.cells).have.length(2);
|
||||||
|
should(model.cells[0].source).be.equal(expectedNotebookContent.cells[0].source);
|
||||||
|
should(model.cells[1].source).be.equal(expectedNotebookContent.cells[1].source);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should load contents but then go to error state if client session startup fails', async function(): Promise<void> {
|
||||||
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
||||||
|
notebookManager.contentManager = mockContentManager.object;
|
||||||
|
|
||||||
|
// Given I have a session that fails to start
|
||||||
|
mockClientSession.setup(c => c.isInErrorState).returns(() => true);
|
||||||
|
mockClientSession.setup(c => c.errorMessage).returns(() => 'Error');
|
||||||
|
sessionReady.resolve();
|
||||||
|
let sessionFired = false;
|
||||||
|
|
||||||
|
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
|
||||||
|
factory: mockModelFactory.object
|
||||||
|
});
|
||||||
|
let model = new NotebookModel(options);
|
||||||
|
model.onClientSessionReady((session) => sessionFired = true);
|
||||||
|
await model.requestModelLoad();
|
||||||
|
model.backgroundStartSession();
|
||||||
|
|
||||||
|
// Then I expect load to succeed
|
||||||
|
shouldHaveOneCell(model);
|
||||||
|
should(model.clientSession).not.be.undefined();
|
||||||
|
// but on server load completion I expect error state to be set
|
||||||
|
// Note: do not expect serverLoad event to throw even if failed
|
||||||
|
await model.sessionLoadFinished;
|
||||||
|
should(model.inErrorState).be.true();
|
||||||
|
should(sessionFired).be.false();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should not be in error state if client session initialization succeeds', async function(): Promise<void> {
|
||||||
|
let mockContentManager = TypeMoq.Mock.ofType(LocalContentManager);
|
||||||
|
mockContentManager.setup(c => c.getNotebookContents(TypeMoq.It.isAny())).returns(() => Promise.resolve(expectedNotebookContentOneCell));
|
||||||
|
notebookManager.contentManager = mockContentManager.object;
|
||||||
|
let kernelChangedEmitter: Emitter<nb.IKernelChangedArgs> = new Emitter<nb.IKernelChangedArgs>();
|
||||||
|
|
||||||
|
mockClientSession.setup(c => c.isInErrorState).returns(() => false);
|
||||||
|
mockClientSession.setup(c => c.isReady).returns(() => true);
|
||||||
|
mockClientSession.setup(c => c.kernelChanged).returns(() => kernelChangedEmitter.event);
|
||||||
|
|
||||||
|
queryConnectionService.setup(c => c.getActiveConnections(TypeMoq.It.isAny())).returns(() => null);
|
||||||
|
|
||||||
|
sessionReady.resolve();
|
||||||
|
let actualSession: IClientSession = undefined;
|
||||||
|
|
||||||
|
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>> {
|
||||||
|
factory: mockModelFactory.object
|
||||||
|
});
|
||||||
|
let model = new NotebookModel(options, false);
|
||||||
|
model.onClientSessionReady((session) => actualSession = session);
|
||||||
|
await model.requestModelLoad();
|
||||||
|
model.backgroundStartSession();
|
||||||
|
|
||||||
|
// Then I expect load to succeed
|
||||||
|
should(model.clientSession).not.be.undefined();
|
||||||
|
// but on server load completion I expect error state to be set
|
||||||
|
// Note: do not expect serverLoad event to throw even if failed
|
||||||
|
let kernelChangedArg: nb.IKernelChangedArgs = undefined;
|
||||||
|
model.kernelChanged((kernel) => kernelChangedArg = kernel);
|
||||||
|
await model.sessionLoadFinished;
|
||||||
|
should(model.inErrorState).be.false();
|
||||||
|
should(actualSession).equal(mockClientSession.object);
|
||||||
|
should(model.clientSession).equal(mockClientSession.object);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should sanitize kernel display name when IP is included', async function(): Promise<void> {
|
||||||
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
|
let displayName = 'PySpark (1.1.1.1)';
|
||||||
|
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
|
||||||
|
should(sanitizedDisplayName).equal('PySpark');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should sanitize kernel display name properly when IP is not included', async function(): Promise<void> {
|
||||||
|
let model = new NotebookModel(defaultModelOptions);
|
||||||
|
let displayName = 'PySpark';
|
||||||
|
let sanitizedDisplayName = model.sanitizeDisplayName(displayName);
|
||||||
|
should(sanitizedDisplayName).equal('PySpark');
|
||||||
|
});
|
||||||
|
|
||||||
|
function shouldHaveOneCell(model: NotebookModel): void {
|
||||||
|
should(model.cells).have.length(1);
|
||||||
|
verifyCellModel(model.cells[0], { cell_type: CellTypes.Code, source: 'insert into t1 values (c1, c2)', metadata: { language: 'python' }, execution_count: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
function verifyCellModel(cellModel: ICellModel, expected: nb.ICellContents): void {
|
||||||
|
should(cellModel.cellType).equal(expected.cell_type);
|
||||||
|
should(cellModel.source).equal(expected.source);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -66,7 +66,7 @@ suite('MainThreadModelViewDialog Tests', () => {
|
|||||||
let extHostContext = <IExtHostContext>{
|
let extHostContext = <IExtHostContext>{
|
||||||
getProxy: proxyType => mockExtHostModelViewDialog.object
|
getProxy: proxyType => mockExtHostModelViewDialog.object
|
||||||
};
|
};
|
||||||
mainThreadModelViewDialog = new MainThreadModelViewDialog(extHostContext, undefined, undefined);
|
mainThreadModelViewDialog = new MainThreadModelViewDialog(extHostContext, undefined, undefined, undefined);
|
||||||
|
|
||||||
// Set up the mock dialog service
|
// Set up the mock dialog service
|
||||||
mockDialogService = Mock.ofType(CustomDialogService, undefined, undefined);
|
mockDialogService = Mock.ofType(CustomDialogService, undefined, undefined);
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ class ExtHostNotebookStub implements ExtHostNotebookShape {
|
|||||||
$doStopServer(managerHandle: number): Thenable<void> {
|
$doStopServer(managerHandle: number): Thenable<void> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebook> {
|
$getNotebookContents(managerHandle: number, notebookUri: UriComponents): Thenable<sqlops.nb.INotebookContents> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebook): Thenable<sqlops.nb.INotebook> {
|
$save(managerHandle: number, notebookUri: UriComponents, notebook: sqlops.nb.INotebookContents): Thenable<sqlops.nb.INotebookContents> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
$refreshSpecs(managerHandle: number): Thenable<sqlops.nb.IAllKernels> {
|
$refreshSpecs(managerHandle: number): Thenable<sqlops.nb.IAllKernels> {
|
||||||
|
|||||||
238
src/typings/should.d.ts
vendored
Executable file
238
src/typings/should.d.ts
vendored
Executable file
@@ -0,0 +1,238 @@
|
|||||||
|
// Type definitions for should.js
|
||||||
|
|
||||||
|
declare function should(obj: any): should.Assertion;
|
||||||
|
|
||||||
|
// node assert methods
|
||||||
|
/*interface NodeAssert {
|
||||||
|
fail(actual: any, expected: any, message?: string, operator?: string): void;
|
||||||
|
ok(value: any, message?: string): void;
|
||||||
|
equal(actual: any, expected: any, message?: string): void;
|
||||||
|
notEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
deepEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
notDeepEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
strictEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
notStrictEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
|
||||||
|
throws(block: Function, message?: string): void;
|
||||||
|
throws(block: Function, error: Function, message?: string): void;
|
||||||
|
throws(block: Function, error: RegExp, message?: string): void;
|
||||||
|
throws(block: Function, error: (err: any) => boolean, message?: string): void;
|
||||||
|
|
||||||
|
doesNotThrow(block: Function, message?: string): void;
|
||||||
|
doesNotThrow(block: Function, error: Function, message?: string): void;
|
||||||
|
doesNotThrow(block: Function, error: RegExp, message?: string): void;
|
||||||
|
doesNotThrow(block: Function, error: (err: any) => boolean, message?: string): void;
|
||||||
|
|
||||||
|
ifError(value: any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
interface should extends NodeAssert, ShouldAssertExt {
|
||||||
|
not: ShouldAssertExt;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
declare module should {
|
||||||
|
interface ShouldAssertExt {
|
||||||
|
exist(obj: any, msg?: string): void;
|
||||||
|
exists(obj: any, msg?: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fail(actual: any, expected: any, message?: string, operator?: string): void;
|
||||||
|
function ok(value: any, message?: string): void;
|
||||||
|
function equal(actual: any, expected: any, message?: string): void;
|
||||||
|
function notEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
function deepEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
function notDeepEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
function strictEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
function notStrictEqual(actual: any, expected: any, message?: string): void;
|
||||||
|
|
||||||
|
function throws(block: Function, message?: string): void;
|
||||||
|
function throws(block: Function, error: Function, message?: string): void;
|
||||||
|
function throws(block: Function, error: RegExp, message?: string): void;
|
||||||
|
function throws(block: Function, error: (err: any) => boolean, message?: string): void;
|
||||||
|
|
||||||
|
function doesNotThrow(block: Function, message?: string): void;
|
||||||
|
function doesNotThrow(block: Function, error: Function, message?: string): void;
|
||||||
|
function doesNotThrow(block: Function, error: RegExp, message?: string): void;
|
||||||
|
function doesNotThrow(block: Function, error: (err: any) => boolean, message?: string): void;
|
||||||
|
|
||||||
|
function ifError(value: any): void;
|
||||||
|
|
||||||
|
function exist(obj: any, msg?: string): void;
|
||||||
|
function exists(obj: any, msg?: string): void;
|
||||||
|
|
||||||
|
const not: ShouldAssertExt;
|
||||||
|
|
||||||
|
interface Assertion {
|
||||||
|
assert(expr: boolean): this;
|
||||||
|
fail(): this;
|
||||||
|
|
||||||
|
not: this;
|
||||||
|
any: this;
|
||||||
|
only: this;
|
||||||
|
|
||||||
|
// bool
|
||||||
|
true(message?: string): this;
|
||||||
|
True(message?: string): this;
|
||||||
|
|
||||||
|
false(message?: string): this;
|
||||||
|
False(message?: string): this;
|
||||||
|
|
||||||
|
ok(): this;
|
||||||
|
|
||||||
|
//chain
|
||||||
|
an: this;
|
||||||
|
of: this;
|
||||||
|
a: this;
|
||||||
|
and: this;
|
||||||
|
be: this;
|
||||||
|
been: this;
|
||||||
|
has: this;
|
||||||
|
have: this;
|
||||||
|
with: this;
|
||||||
|
is: this;
|
||||||
|
which: this;
|
||||||
|
the: this;
|
||||||
|
it: this;
|
||||||
|
|
||||||
|
//contain
|
||||||
|
containEql(obj: any): this;
|
||||||
|
containDeepOrdered(obj: any): this;
|
||||||
|
containDeep(obj: any): this;
|
||||||
|
|
||||||
|
// eql
|
||||||
|
eql(obj: any, description?: string): this;
|
||||||
|
eqls(obj: any, description?: string): this;
|
||||||
|
deepEqual(obj: any, description?: string): this;
|
||||||
|
|
||||||
|
equal(obj: any, description?: string): this;
|
||||||
|
equals(obj: any, description?: string): this;
|
||||||
|
exactly(obj: any, description?: string): this;
|
||||||
|
|
||||||
|
equalOneOf(...objs: any[]): this;
|
||||||
|
equalOneOf(obj: any[]): this;
|
||||||
|
oneOf(...objs: any[]): this;
|
||||||
|
oneOf(obj: any[]): this;
|
||||||
|
|
||||||
|
//error
|
||||||
|
throw(): this;
|
||||||
|
throw(msg: RegExp | string | Function, properties?: {}): this;
|
||||||
|
throw(properties: {}): this;
|
||||||
|
//TODO how to express generators???
|
||||||
|
throwError(): this;
|
||||||
|
throwError(msg: RegExp | string | Function, properties?: {}): this;
|
||||||
|
throwError(properties: {}): this;
|
||||||
|
|
||||||
|
// match
|
||||||
|
match(
|
||||||
|
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
|
||||||
|
description?: string
|
||||||
|
): this;
|
||||||
|
matchEach(
|
||||||
|
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
|
||||||
|
description?: string
|
||||||
|
): this;
|
||||||
|
matchEvery(
|
||||||
|
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
|
||||||
|
description?: string
|
||||||
|
): this;
|
||||||
|
matchAny(
|
||||||
|
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
|
||||||
|
description?: string
|
||||||
|
): this;
|
||||||
|
matchSome(
|
||||||
|
obj: RegExp | ((value: any, key: any) => boolean) | ((value: any, key: any) => void) | {},
|
||||||
|
description?: string
|
||||||
|
): this;
|
||||||
|
|
||||||
|
//number
|
||||||
|
NaN(): this;
|
||||||
|
Infinity(): this;
|
||||||
|
within(start: number, finish: number, description?: string): this;
|
||||||
|
approximately(value: number, delta: number, description?: string): this;
|
||||||
|
above(value: number, description?: string): this;
|
||||||
|
greaterThan(value: number, description?: string): this;
|
||||||
|
below(value: number, description?: string): this;
|
||||||
|
lessThan(value: number, description?: string): this;
|
||||||
|
aboveOrEqual(value: number, description?: string): this;
|
||||||
|
greaterThanOrEqual(value: number, description?: string): this;
|
||||||
|
belowOrEqual(value: number, description?: string): this;
|
||||||
|
lessThanOrEqual(value: number, description?: string): this;
|
||||||
|
|
||||||
|
//promise
|
||||||
|
Promise(): this;
|
||||||
|
|
||||||
|
fulfilled(): Promise<any>;
|
||||||
|
resolved(): Promise<any>;
|
||||||
|
rejected(): Promise<any>;
|
||||||
|
|
||||||
|
fulfilledWith(obj: any): Promise<any>;
|
||||||
|
resolvedWith(obj: any): Promise<any>;
|
||||||
|
rejectedWith(msg: RegExp | string | Error, properties?: {}): Promise<any>;
|
||||||
|
rejectedWith(properties: {}): Promise<any>;
|
||||||
|
finally: PromisedAssertion;
|
||||||
|
eventually: PromisedAssertion;
|
||||||
|
|
||||||
|
// property
|
||||||
|
propertyWithDescriptor(name: string, descriptor: {}): this;
|
||||||
|
|
||||||
|
property(name: string, value?: any): this;
|
||||||
|
properties(...names: string[]): this;
|
||||||
|
properties(names: string[]): this;
|
||||||
|
properties(props: {}): this;
|
||||||
|
|
||||||
|
length(value: number, description?: string): this;
|
||||||
|
lengthOf(value: number, description?: string): this;
|
||||||
|
|
||||||
|
ownProperty(name: string, description?: string): this;
|
||||||
|
hasOwnProperty(name: string, description?: string): this;
|
||||||
|
|
||||||
|
empty(): this;
|
||||||
|
|
||||||
|
keys(...keys: any[]): this;
|
||||||
|
key(key: any): this;
|
||||||
|
|
||||||
|
value(key: any, value: any): this;
|
||||||
|
|
||||||
|
size(value: number): this;
|
||||||
|
|
||||||
|
propertyByPath(...path: string[]): this;
|
||||||
|
propertyByPath(path: string[]): this;
|
||||||
|
|
||||||
|
//string
|
||||||
|
startWith(prefix: string, description?: string): this;
|
||||||
|
endWith(postfix: string, description?: string): this;
|
||||||
|
|
||||||
|
//type
|
||||||
|
Number(): this;
|
||||||
|
arguments(): this;
|
||||||
|
Arguments(): this;
|
||||||
|
type(typeName: string, description?: string): this;
|
||||||
|
instanceof(constructor: Function, description?: string): this;
|
||||||
|
instanceOf(constructor: Function, description?: string): this;
|
||||||
|
Function(): this;
|
||||||
|
Object(): this;
|
||||||
|
String(): this;
|
||||||
|
Array(): this;
|
||||||
|
Boolean(): this;
|
||||||
|
Error(): this;
|
||||||
|
Date(): this;
|
||||||
|
null(): this;
|
||||||
|
Null(): this;
|
||||||
|
class(className: string): this;
|
||||||
|
Class(className: string): this;
|
||||||
|
undefined(): this;
|
||||||
|
Undefined(): this;
|
||||||
|
iterable(): this;
|
||||||
|
iterator(): this;
|
||||||
|
generator(): this;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PromisedAssertion extends Assertion, PromiseLike<any> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
declare module 'should' {
|
||||||
|
export = should;
|
||||||
|
}
|
||||||
3
src/typings/temp-write.d.ts
vendored
Normal file
3
src/typings/temp-write.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
declare module 'temp-write' {
|
||||||
|
function sync(input: string, filePath?: string): string;
|
||||||
|
}
|
||||||
82
yarn.lock
82
yarn.lock
@@ -99,6 +99,13 @@
|
|||||||
version "5.5.0"
|
version "5.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
|
||||||
|
|
||||||
|
"@types/should@^13.0.0":
|
||||||
|
version "13.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/should/-/should-13.0.0.tgz#96c00117f1896177848fdecfa336313c230c879e"
|
||||||
|
integrity sha512-Mi6YZ2ABnnGGFMuiBDP0a8s1ZDCDNHqP97UH8TyDmCWuGGavpsFMfJnAMYaaqmDlSCOCNbVLHBrSDEOpx/oLhw==
|
||||||
|
dependencies:
|
||||||
|
should "*"
|
||||||
|
|
||||||
"@types/sinon@1.16.34":
|
"@types/sinon@1.16.34":
|
||||||
version "1.16.34"
|
version "1.16.34"
|
||||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-1.16.34.tgz#a9761fff33d0f7b3fe61875b577778a2576a9a03"
|
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-1.16.34.tgz#a9761fff33d0f7b3fe61875b577778a2576a9a03"
|
||||||
@@ -4373,6 +4380,13 @@ macaddress@^0.2.8:
|
|||||||
version "0.2.8"
|
version "0.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
make-error-cause@^1.1.1:
|
make-error-cause@^1.1.1:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d"
|
resolved "https://registry.yarnpkg.com/make-error-cause/-/make-error-cause-1.2.2.tgz#df0388fcd0b37816dff0a5fb8108939777dcbc9d"
|
||||||
@@ -5265,6 +5279,11 @@ pify@^2.0.0:
|
|||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
|
|
||||||
|
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:
|
pinkie-promise@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||||
@@ -6350,6 +6369,50 @@ shelljs@^0.7.5:
|
|||||||
interpret "^1.0.0"
|
interpret "^1.0.0"
|
||||||
rechoir "^0.6.2"
|
rechoir "^0.6.2"
|
||||||
|
|
||||||
|
should-equal@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3"
|
||||||
|
integrity sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==
|
||||||
|
dependencies:
|
||||||
|
should-type "^1.4.0"
|
||||||
|
|
||||||
|
should-format@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/should-format/-/should-format-3.0.3.tgz#9bfc8f74fa39205c53d38c34d717303e277124f1"
|
||||||
|
integrity sha1-m/yPdPo5IFxT04w01xcwPidxJPE=
|
||||||
|
dependencies:
|
||||||
|
should-type "^1.3.0"
|
||||||
|
should-type-adaptors "^1.0.1"
|
||||||
|
|
||||||
|
should-type-adaptors@^1.0.1:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz#401e7f33b5533033944d5cd8bf2b65027792e27a"
|
||||||
|
integrity sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==
|
||||||
|
dependencies:
|
||||||
|
should-type "^1.3.0"
|
||||||
|
should-util "^1.0.0"
|
||||||
|
|
||||||
|
should-type@^1.3.0, should-type@^1.4.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/should-type/-/should-type-1.4.0.tgz#0756d8ce846dfd09843a6947719dfa0d4cff5cf3"
|
||||||
|
integrity sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=
|
||||||
|
|
||||||
|
should-util@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/should-util/-/should-util-1.0.0.tgz#c98cda374aa6b190df8ba87c9889c2b4db620063"
|
||||||
|
integrity sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=
|
||||||
|
|
||||||
|
should@*, should@^13.2.3:
|
||||||
|
version "13.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/should/-/should-13.2.3.tgz#96d8e5acf3e97b49d89b51feaa5ae8d07ef58f10"
|
||||||
|
integrity sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==
|
||||||
|
dependencies:
|
||||||
|
should-equal "^2.0.0"
|
||||||
|
should-format "^3.0.3"
|
||||||
|
should-type "^1.4.0"
|
||||||
|
should-type-adaptors "^1.0.1"
|
||||||
|
should-util "^1.0.0"
|
||||||
|
|
||||||
sigmund@^1.0.1, sigmund@~1.0.0:
|
sigmund@^1.0.1, sigmund@~1.0.0:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
|
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
|
||||||
@@ -6804,6 +6867,23 @@ tar-stream@^1.1.2:
|
|||||||
to-buffer "^1.1.0"
|
to-buffer "^1.1.0"
|
||||||
xtend "^4.0.0"
|
xtend "^4.0.0"
|
||||||
|
|
||||||
|
temp-dir@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-1.0.0.tgz#0a7c0ea26d3a39afa7e0ebea9c1fc0bc4daa011d"
|
||||||
|
integrity sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=
|
||||||
|
|
||||||
|
temp-write@^3.4.0:
|
||||||
|
version "3.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/temp-write/-/temp-write-3.4.0.tgz#8cff630fb7e9da05f047c74ce4ce4d685457d492"
|
||||||
|
integrity sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=
|
||||||
|
dependencies:
|
||||||
|
graceful-fs "^4.1.2"
|
||||||
|
is-stream "^1.1.0"
|
||||||
|
make-dir "^1.0.0"
|
||||||
|
pify "^3.0.0"
|
||||||
|
temp-dir "^1.0.0"
|
||||||
|
uuid "^3.0.1"
|
||||||
|
|
||||||
temp@^0.8.3:
|
temp@^0.8.3:
|
||||||
version "0.8.3"
|
version "0.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
|
resolved "https://registry.yarnpkg.com/temp/-/temp-0.8.3.tgz#e0c6bc4d26b903124410e4fed81103014dfc1f59"
|
||||||
@@ -7228,7 +7308,7 @@ uuid@^3.0.0, uuid@^3.1.0:
|
|||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
|
||||||
|
|
||||||
uuid@^3.3.2:
|
uuid@^3.0.1, uuid@^3.3.2:
|
||||||
version "3.3.2"
|
version "3.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user