Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Trust Book in Book Viewlet #9414

Merged
merged 39 commits into from
Mar 13, 2020
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ba400ef
Initial Work - Trusted Mode for Workspace
jberumen3 Feb 14, 2020
242e241
Updating default value of trusted notebooks
joberume Feb 18, 2020
2f9ac4e
Updating default value for trusted notebooks
joberume Feb 19, 2020
9e3a9bd
Merge branch 'dev/joberume/trust-notebook-directories' of https://git…
joberume Feb 19, 2020
84961f2
Merge branch 'master' into dev/joberume/trust-notebook-directories
joberume Feb 19, 2020
b622cb9
Removed bad merge resolution
joberume Feb 19, 2020
c6c1f5d
Merge branch 'dev/joberume/trust-notebook-directories' into dev/jober…
jberumen3 Feb 26, 2020
398ea2d
Moving trusting logic from contribution to extension
jberumen3 Mar 1, 2020
531b5ff
Getting rid of extra comma
jberumen3 Mar 1, 2020
19b5899
Resolved merge conflicts
jberumen3 Mar 2, 2020
1510c47
Getting rid of whitespace/noop changes
jberumen3 Mar 2, 2020
b1f9864
Resolving linting issues
joberume Mar 2, 2020
2c80afd
Addressing pull request comments
jberumen3 Mar 3, 2020
72105ca
Resolving merge conflicts
jberumen3 Mar 3, 2020
15329fc
Fixing missed pull request comment - removing default value
jberumen3 Mar 3, 2020
654674a
Switch workspace details to getters as they may be stale at initializ…
jberumen3 Mar 3, 2020
30d57f7
Addressed pull request comments. Refactored to use notebooks instead …
jberumen3 Mar 4, 2020
9fa4f92
Addressed more PR comments
jberumen3 Mar 5, 2020
0839309
Small changes
jberumen3 Mar 5, 2020
7c80069
Fixing existing tests
jberumen3 Mar 5, 2020
d481ae7
Using mementos to remember visited notebooks
jberumen3 Mar 5, 2020
473a9e1
Added test cases
jberumen3 Mar 6, 2020
c73748f
Merge remote-tracking branch 'origin/master' into dev/joberume/trust-…
jberumen3 Mar 6, 2020
68840d0
Removed unnecessary comments
jberumen3 Mar 6, 2020
f1cf045
Few Minor changes
jberumen3 Mar 7, 2020
53f6261
Fixing tests + addressing some comments
joberume Mar 9, 2020
711e75b
Refactor some logic out of bookTrustManager into BookTreeItem
joberume Mar 9, 2020
7ebe11d
Removing extra untrust method
joberume Mar 9, 2020
d952e35
Using ApiWrapper in book trust manager + using mocks
joberume Mar 9, 2020
0da7f4e
Small changes
joberume Mar 9, 2020
9a08197
Fix typescript lint
joberume Mar 9, 2020
63c22ef
Remove remanant debug message
joberume Mar 9, 2020
674169f
Remembering trusted books in folder mode
joberume Mar 10, 2020
411fe48
Updating tests to leverage configuration provider in folders
joberume Mar 10, 2020
61209a3
Merge remote-tracking branch 'origin/master' into dev/joberume/trust-…
joberume Mar 10, 2020
f5d61ae
Refactoring logic out to BookModel + fixing tests
joberume Mar 10, 2020
a4a539b
Completely removing untrust logic
joberume Mar 10, 2020
ee4bb79
Allow tests to pass in windows
jberumen3 Mar 11, 2020
99ea3d6
Reflect changes immediately when automatically trusted
jberumen3 Mar 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions extensions/notebook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@
"type": "number",
"default": 5000,
"description": "%notebook.maxTableRows.description%"
},
"notebook.trustedBooks":{
"type": "array",
"default": [],
"description": "%notebook.trustedBooks.description%",
"items": {
"type": "string"
}
}
}
},
Expand Down Expand Up @@ -154,6 +162,15 @@
"light": "resources/light/save.svg"
}
},
{
"command": "notebook.command.trustBook",
Charles-Gagnon marked this conversation as resolved.
Show resolved Hide resolved
"title": "%title.trustBook%",
"category": "%books-preview-category%",
"icon": {
"dark": "resources/dark/trust_inverse.svg",
"light": "resources/light/trust.svg"
}
},
{
"command": "notebook.command.searchBook",
"title": "%title.searchJupyterBook%",
Expand Down Expand Up @@ -273,6 +290,10 @@
{
"command": "notebook.command.searchUntitledBook",
"when": "false"
},
{
"command": "notebook.command.trustBook",
"when": "false"
joberume marked this conversation as resolved.
Show resolved Hide resolved
}
],
"touchBar": [
Expand Down Expand Up @@ -300,6 +321,11 @@
}
],
"view/item/context": [
{
"command": "notebook.command.trustBook",
"when": "view == bookTreeView && viewItem == savedBook",
"group": "inline"
},
{
"command": "notebook.command.searchBook",
"when": "view == bookTreeView && viewItem == savedBook || viewItem == section",
Expand Down
2 changes: 2 additions & 0 deletions extensions/notebook/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"notebook.useExistingPython.description": "Local path to a preexisting python installation used by Notebooks.",
"notebook.overrideEditorTheming.description": "Override editor default settings in the Notebook editor. Settings include background color, current line color and border",
"notebook.maxTableRows.description": "Maximum number of rows returned per table in the Notebook editor",
"notebook.trustedBooks.description": "Books to trust automatically.",
joberume marked this conversation as resolved.
Show resolved Hide resolved
"notebook.maxBookSearchDepth.description": "Maximum depth of subdirectories to search for Books (Enter 0 for infinite)",
"notebook.command.new": "New Notebook",
"notebook.command.open": "Open Notebook",
Expand All @@ -30,6 +31,7 @@
"title.SQL19PreviewBook": "SQL Server 2019 Guide",
"books-preview-category": "Jupyter Books",
"title.saveJupyterBook": "Save Book",
"title.trustBook": "Trust Book",
"title.searchJupyterBook": "Search Book",
"title.SavedBooks": "Saved Books",
"title.UnsavedBooks": "Unsaved Books",
Expand Down
3 changes: 3 additions & 0 deletions extensions/notebook/resources/dark/trust_inverse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions extensions/notebook/resources/light/trust.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 31 additions & 6 deletions extensions/notebook/src/book/bookTreeView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs-extra';
import * as nls from 'vscode-nls';
import { IPrompter, QuestionTypes, IQuestion } from '../prompts/question';
import CodeAdapter from '../prompts/adapter';
import { BookTreeItem } from './bookTreeItem';
import { BookModel } from './bookModel';
import { Deferred } from '../common/promise';
import { IBookTrustManager, BookTrustManager } from './bookTrustManager';
import * as loc from '../common/localizedConstants';

const localize = nls.loadMessageBundle();
const msgBookTrusted = localize('msgBookTrusted', "Book is now automatically trusted.");
joberume marked this conversation as resolved.
Show resolved Hide resolved
const msgBookAlreadyTrusted = localize('msgBookAlreadyTrusted', "Book is already trusted.");
const Content = 'content';

export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeItem> {

private _onDidChangeTreeData: vscode.EventEmitter<BookTreeItem | undefined> = new vscode.EventEmitter<BookTreeItem | undefined>();
readonly onDidChangeTreeData: vscode.Event<BookTreeItem | undefined> = this._onDidChangeTreeData.event;
private _throttleTimer: any;
Expand All @@ -27,6 +31,9 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
private _initializeDeferred: Deferred<void> = new Deferred<void>();

private _openAsUntitled: boolean;
private _trustResourceCache: Record<string, boolean> = {};
joberume marked this conversation as resolved.
Show resolved Hide resolved
private _bookTrustManager: IBookTrustManager;

public viewId: string;
public books: BookModel[];
public currentBook: BookModel;
Expand All @@ -38,7 +45,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
this.initialize(workspaceFolders).catch(e => console.error(e));
this.viewId = view;
this.prompter = new CodeAdapter();

this._bookTrustManager = new BookTrustManager(this.books);
}

private async initialize(workspaceFolders: vscode.WorkspaceFolder[]): Promise<void> {
Expand All @@ -57,6 +64,16 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
return this._initializeDeferred.promise;
}

trustBook(bookTreeItem: BookTreeItem): any {
joberume marked this conversation as resolved.
Show resolved Hide resolved
let hasTrustedBook = this._bookTrustManager.setBookAsTrusted(bookTreeItem);

if (hasTrustedBook) {
vscode.window.showInformationMessage(msgBookTrusted);
joberume marked this conversation as resolved.
Show resolved Hide resolved
} else {
vscode.window.showInformationMessage(msgBookAlreadyTrusted);
}
}

async openBook(bookPath: string, urlToOpen?: string): Promise<void> {
try {
let books: BookModel[] = this.books.filter(book => book.bookPath === bookPath) || [];
Expand Down Expand Up @@ -112,8 +129,18 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
try {
if (this._openAsUntitled) {
this.openNotebookAsUntitled(resource);
}
else {
} else {
// leave alone cached trusted statuses
let cache = this._trustResourceCache;
let normalizedResource = path.normalize(resource);
if (!cache[normalizedResource] && this._bookTrustManager.isNotebookTrustedByDefault(normalizedResource)) {
let openDocumentListenerUnsubscriber = azdata.nb.onDidOpenNotebookDocument(function (document: azdata.nb.NotebookDocument) {
document.setTrusted(true);
joberume marked this conversation as resolved.
Show resolved Hide resolved
cache[normalizedResource] = true;
openDocumentListenerUnsubscriber.dispose();
});
}

let doc = await vscode.workspace.openTextDocument(resource);
vscode.window.showTextDocument(doc);
}
Expand Down Expand Up @@ -314,6 +341,4 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
default: false
});
}


}
89 changes: 89 additions & 0 deletions extensions/notebook/src/book/bookTrustManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
joberume marked this conversation as resolved.
Show resolved Hide resolved
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as vscode from 'vscode';
import { BookTreeItem } from './bookTreeItem';
import { BookModel } from './bookModel';

export interface IBookTrustManager {
isNotebookTrustedByDefault(notebookUri: string): boolean;
setBookAsTrusted(bookTreeItem: BookTreeItem): boolean;
}

export class BookTrustManager implements IBookTrustManager {

private static notebookConfiguration: string = 'notebook';
joberume marked this conversation as resolved.
Show resolved Hide resolved
private static notebookTrustedBooksConfiguration: string = 'trustedBooks';
joberume marked this conversation as resolved.
Show resolved Hide resolved

constructor(private books: BookModel[]) { }

isNotebookTrustedByDefault(notebookUri: string): boolean {
let workspace = this.getNotebookWorkspaceFolder(notebookUri);
let normalizedWorkspacePath = path.normalize(workspace.uri.fsPath);
let trustedBookDirectory = this.getTrustedBookDirectory(notebookUri, workspace);

if (workspace && trustedBookDirectory) {
let trustedBook = this.getTrustedBook(normalizedWorkspacePath, trustedBookDirectory);

if (trustedBook) {
let fullBookBaseUri = path.join(normalizedWorkspacePath, trustedBookDirectory, 'content');
let requestingNotebookFormattedUri = notebookUri.substring(fullBookBaseUri.length).replace('.ipynb', '');
let notebookInTOC = trustedBook.tableOfContents.sections.find(jupyterSection => {
let normalizedJupyterSectionUrl = jupyterSection.url && path.normalize(jupyterSection.url);
return normalizedJupyterSectionUrl === requestingNotebookFormattedUri;
});
return !!notebookInTOC;
}
}

return false;
}

setBookAsTrusted(bookTreeItem: BookTreeItem): boolean {
// add this TOC to the configuration list
let workspacePathLength: number = path.resolve(vscode.workspace.rootPath).length;
let relativeBookPath: string = path.normalize(path.resolve(bookTreeItem.book.root).substring(workspacePathLength + 1));
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(BookTrustManager.notebookConfiguration);
let existingNotebooks: string[] = config[BookTrustManager.notebookTrustedBooksConfiguration];

// if no match found in the configuration, then add it
if (!existingNotebooks.find(notebookPath => path.normalize(notebookPath) === relativeBookPath)) {
existingNotebooks.push(relativeBookPath);

// update the configuration
config.update(BookTrustManager.notebookTrustedBooksConfiguration, existingNotebooks);
return true;
}
return false;
}

getTrustedBookDirectory(notebookUri: string, workspace: vscode.WorkspaceFolder): string {
let config: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration(BookTrustManager.notebookConfiguration);
let trustedBookDirectories: string[] = config.get(BookTrustManager.notebookTrustedBooksConfiguration);
let notebookUriWithoutBase: string = notebookUri.substring(path.normalize(workspace.uri.fsPath).length + 1);
return trustedBookDirectories.find(dir => notebookUriWithoutBase.startsWith(dir));
joberume marked this conversation as resolved.
Show resolved Hide resolved
}

getTrustedBook(workspaceUri: string, baseBookUri: string): BookTreeItem {
let trustedBook = this.books
.map(book => book.bookItems) // select all the books
.reduce((accumulator, currentBookItemList) => accumulator.concat(currentBookItemList)) // flatten them to a single list
.find(bookTreeItem => {
let normalizedRootPath = path.normalize(bookTreeItem.root);
let fqnBookRootPath = path.join(workspaceUri, baseBookUri);
return normalizedRootPath.startsWith(fqnBookRootPath);
});
return trustedBook;
}

getNotebookWorkspaceFolder(notebookUri: string): vscode.WorkspaceFolder {
let workspace = vscode.workspace;
let workspaceFolder = workspace.workspaceFolders.find(wsFolder => {
joberume marked this conversation as resolved.
Show resolved Hide resolved
let normalizedWsFolderUri = path.normalize(wsFolder.uri.fsPath);
return notebookUri.startsWith(normalizedWsFolderUri);
});
return workspaceFolder;
}
}
1 change: 1 addition & 0 deletions extensions/notebook/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export async function activate(extensionContext: vscode.ExtensionContext): Promi
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openMarkdown', (resource) => bookTreeViewProvider.openMarkdown(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('bookTreeView.openExternalLink', (resource) => bookTreeViewProvider.openExternalLink(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.saveBook', () => untitledBookTreeViewProvider.saveJupyterBooks()));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.trustBook', (resource) => bookTreeViewProvider.trustBook(resource)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchBook', (item) => bookTreeViewProvider.searchJupyterBooks(item)));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.searchUntitledBook', () => untitledBookTreeViewProvider.searchJupyterBooks()));
extensionContext.subscriptions.push(vscode.commands.registerCommand('notebook.command.openBook', () => bookTreeViewProvider.openNewBook()));
Expand Down
5 changes: 5 additions & 0 deletions src/sql/azdata.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4314,6 +4314,11 @@ declare module 'azdata' {
* @return The given range or a new, adjusted range.
*/
validateCellRange(range: CellRange): CellRange;

/**
* Sets the notebook to trusted by default.
*/
setTrusted(state: boolean);
joberume marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,17 @@ export class MainThreadNotebookDocumentsAndEditors extends Disposable implements
return Promise.resolve(this.doOpenEditor(resource, options));
}

$trySetTrusted(uri: UriComponents, isTrusted: boolean = true): Promise<boolean> {
joberume marked this conversation as resolved.
Show resolved Hide resolved
let uriString = URI.revive(uri).toString();
let editor: MainThreadNotebookEditor = this._notebookEditors.get(uriString);
if (editor) {
editor.model.trustedMode = isTrusted;
return Promise.resolve(isTrusted);
} else {
return Promise.resolve(false);
}
}

$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): Promise<boolean> {
let editor = this.getEditor(id);
if (!editor) {
Expand Down
7 changes: 7 additions & 0 deletions src/sql/workbench/api/common/extHostNotebookDocumentData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export class ExtHostNotebookDocumentData implements IDisposable {
get cells() { return data._cells; },
get kernelSpec() { return data._kernelSpec; },
save() { return data._save(); },
setTrusted(isTrusted) { data._setTrusted(isTrusted); },
joberume marked this conversation as resolved.
Show resolved Hide resolved
validateCellRange(range) { return data._validateRange(range); },
};
}
Expand All @@ -61,7 +62,13 @@ export class ExtHostNotebookDocumentData implements IDisposable {
return Promise.reject(new Error('Document has been closed'));
}
return this._proxy.$trySaveDocument(this._uri);
}

private _setTrusted(isTrusted: boolean): Thenable<boolean> {
if (this._isDisposed) {
return Promise.reject(new Error('Document has been closed'));
}
return this._proxy.$trySetTrusted(this._uri, isTrusted);
}

public onModelChanged(data: INotebookModelChangedData) {
Expand Down
4 changes: 4 additions & 0 deletions src/sql/workbench/api/common/extHostNotebookEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ export class NotebookEditorEdit {
return range;
}

setTrusted(isTrusted: boolean) {
this._document.setTrusted(isTrusted);
}

insertCell(value: Partial<azdata.nb.ICellContents>, index?: number, collapsed?: boolean): void {
if (index === null || index === undefined) {
// If not specified, assume adding to end of list
Expand Down
1 change: 1 addition & 0 deletions src/sql/workbench/api/common/sqlExtHost.protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,7 @@ export interface ExtHostNotebookDocumentsAndEditorsShape {
}

export interface MainThreadNotebookDocumentsAndEditorsShape extends IDisposable {
$trySetTrusted(_uri: UriComponents, isTrusted: boolean): Thenable<boolean>;
$trySaveDocument(uri: UriComponents): Thenable<boolean>;
$tryShowNotebookDocument(resource: UriComponents, options: INotebookShowOptions): Promise<string>;
$tryApplyEdits(id: string, modelVersionId: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): Promise<boolean>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export class NotebookModel extends Disposable implements INotebookModel {

public set trustedMode(isTrusted: boolean) {
this._trustedMode = isTrusted;

if (this._cells) {
this._cells.forEach(c => {
c.trustedMode = this._trustedMode;
Expand Down Expand Up @@ -290,6 +291,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
public async loadContents(isTrusted: boolean = false): Promise<void> {
try {
this._trustedMode = isTrusted;

let contents = null;

if (this._notebookOptions && this._notebookOptions.contentManager) {
Expand Down