Fix #3439 Trusted doesn't get saved in Notebooks (#5507)

* Fix #3439 Trusted doesn't get saved in Notebooks
The main fix is to add a memento to store trust information. This is only needed for saved files - untitled files are always trusted as the user created them.
On clicking trusted or saving a file, the trusted state is cached. In the future, we will also handle code execution here too by sending notification on snapshot state.
I found issue #5506 during testing - existing issue where we should track trusted state changing on run. In the case all cells are ran, the whole notebook should become trusted.

Finally, I did a decent amount of refactoring to move more logic to the model - removing unnecessary calls from components which duplicated model behavior, moving trust notification to the model or at least the notebook service completely.

Added tests and logging for catch handling
This commit is contained in:
Kevin Cunnane
2019-05-17 11:56:47 -07:00
committed by GitHub
parent 94061fa634
commit 8ea831c845
11 changed files with 225 additions and 58 deletions

View File

@@ -42,5 +42,6 @@ export enum NotebookChangeType {
CellSourceUpdated,
CellOutputUpdated,
DirtyStateChanged,
KernelChanged
KernelChanged,
TrustChanged
}

View File

@@ -235,6 +235,9 @@ export class NotebookModel extends Disposable implements INotebookModel {
c.trustedMode = this._trustedMode;
});
}
this._contentChangedEmitter.fire({
changeType: NotebookChangeType.TrustChanged
});
}
public get activeConnection(): IConnectionProfile {

View File

@@ -188,19 +188,6 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
this._model.addCell(cellType);
}
// Updates Notebook model's trust details
public updateModelTrustDetails(isTrusted: boolean) {
this._model.trustedMode = isTrusted;
this._model.cells.forEach(cell => {
cell.trustedMode = isTrusted;
});
//Updates dirty state
if (this._notebookParams.input) {
this._notebookParams.input.updateModel();
}
this.detectChanges();
}
public onKeyDown(event) {
switch (event.key) {
case 'ArrowDown':
@@ -294,7 +281,8 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
capabilitiesService: this.capabilitiesService
}, this.profile, this.logService);
model.onError((errInfo: INotification) => this.handleModelError(errInfo));
await model.requestModelLoad(this._notebookParams.isTrusted);
let trusted = await this.notebookService.isNotebookTrustCached(this._notebookParams.notebookUri, this.isDirty());
await model.requestModelLoad(trusted);
model.contentChanged((change) => this.handleContentChanged(change));
model.onProviderIdChange((provider) => this.handleProviderIdChanged(provider));
this._model = this._register(model);

View File

@@ -238,7 +238,7 @@ export class TrustedAction extends ToggleableAction {
}
else {
self.trusted = !self.trusted;
context.updateModelTrustDetails(self.trusted);
context.model.trustedMode = self.trusted;
}
resolve(true);
} catch (e) {

View File

@@ -91,7 +91,6 @@ export class NotebookEditor extends BaseEditor {
notebookUri: input.notebookUri,
input: input,
providerInfo: input.getProviderInfo(),
isTrusted: input.isTrusted,
profile: input.connectionProfile
};
bootstrapAngular(this.instantiationService,

View File

@@ -11,20 +11,21 @@ import * as resources from 'vs/base/common/resources';
import * as azdata from 'azdata';
import { IStandardKernelWithProvider, getProvidersForFileName, getStandardKernelsForProvider } from 'sql/workbench/parts/notebook/notebookUtils';
import { INotebookService, DEFAULT_NOTEBOOK_PROVIDER, IProviderInfo } from 'sql/workbench/services/notebook/common/notebookService';
import { INotebookService, DEFAULT_NOTEBOOK_PROVIDER, IProviderInfo, SerializationStateChangeType } from 'sql/workbench/services/notebook/common/notebookService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { INotebookModel, IContentManager } from 'sql/workbench/parts/notebook/models/modelInterfaces';
import { INotebookModel, IContentManager, NotebookContentChange } from 'sql/workbench/parts/notebook/models/modelInterfaces';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { Range } from 'vs/editor/common/core/range';
import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel';
import { Schemas } from 'vs/base/common/network';
import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, ISaveOptions, StateChange } from 'vs/workbench/services/textfile/common/textfiles';
import { LocalContentManager } from 'sql/workbench/services/notebook/node/localContentManager';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IDisposable } from 'vs/base/common/lifecycle';
import { NotebookChangeType } from 'sql/workbench/parts/notebook/models/contracts';
export type ModeViewSaveHandler = (handle: number) => Thenable<boolean>;
@@ -42,7 +43,7 @@ export class NotebookEditorModel extends EditorModel {
// Hook to content change events
notebook.modelReady.then(() => {
this._register(notebook.model.kernelChanged(e => this.updateModel()));
this._register(notebook.model.contentChanged(e => this.updateModel()));
this._register(notebook.model.contentChanged(e => this.updateModel(e)));
}, err => undefined);
}
}));
@@ -50,7 +51,12 @@ export class NotebookEditorModel extends EditorModel {
if (this.textEditorModel instanceof UntitledEditorModel) {
this._register(this.textEditorModel.onDidChangeDirty(e => this.setDirty(this.textEditorModel.isDirty())));
} else {
this._register(this.textEditorModel.onDidStateChange(e => this.setDirty(this.textEditorModel.isDirty())));
this._register(this.textEditorModel.onDidStateChange(change => {
this.setDirty(this.textEditorModel.isDirty());
if (change === StateChange.SAVED) {
this.sendNotebookSerializationStateChange();
}
}));
}
this.dirty = this.textEditorModel.isDirty();
}
@@ -86,18 +92,33 @@ export class NotebookEditorModel extends EditorModel {
}
}
public updateModel(): void {
let notebookModel = this.getNotebookModel();
if (notebookModel && this.textEditorModel && this.textEditorModel.textEditorModel) {
let content = JSON.stringify(notebookModel.toJSON(), undefined, ' ');
let model = this.textEditorModel.textEditorModel;
let endLine = model.getLineCount();
let endCol = model.getLineMaxColumn(endLine);
public updateModel(contentChange?: NotebookContentChange): void {
if (contentChange && contentChange.changeType === NotebookChangeType.TrustChanged) {
// This is a serializable change (in that we permanently cache trusted state, but
// ironically isn't cached in the JSON contents since trust doesn't persist across machines.
// Request serialization so trusted state is preserved but don't update the model
this.sendNotebookSerializationStateChange();
} else {
// For all other changes, update the backing model with the latest contents
let notebookModel = this.getNotebookModel();
if (notebookModel && this.textEditorModel && this.textEditorModel.textEditorModel) {
let content = JSON.stringify(notebookModel.toJSON(), undefined, ' ');
let model = this.textEditorModel.textEditorModel;
let endLine = model.getLineCount();
let endCol = model.getLineMaxColumn(endLine);
this.textEditorModel.textEditorModel.applyEdits([{
range: new Range(1, 1, endLine, endCol),
text: content
}]);
this.textEditorModel.textEditorModel.applyEdits([{
range: new Range(1, 1, endLine, endCol),
text: content
}]);
}
}
}
private sendNotebookSerializationStateChange() {
let notebookModel = this.getNotebookModel();
if (notebookModel) {
this.notebookService.serializeNotebookStateChange(this.notebookUri, SerializationStateChangeType.Saved);
}
}
@@ -125,7 +146,6 @@ export class NotebookInput extends EditorInput {
private _standardKernels: IStandardKernelWithProvider[];
private _connectionProfile: IConnectionProfile;
private _defaultKernel: azdata.nb.IKernelSpec;
private _isTrusted: boolean = false;
public hasBootstrapped = false;
// Holds the HTML content for the editor when the editor discards this input and loads another
private _parentContainer: HTMLElement;
@@ -190,13 +210,6 @@ export class NotebookInput extends EditorInput {
providers: this._providers ? this._providers : [DEFAULT_NOTEBOOK_PROVIDER]
};
}
public get isTrusted(): boolean {
return this._isTrusted;
}
public set isTrusted(value: boolean) {
this._isTrusted = value;
}
public set connectionProfile(value: IConnectionProfile) {
this._connectionProfile = value;