Skip to content

Commit 0a1bbbd

Browse files
authored
Trust for native notebooks (microsoft#14353)
1 parent 5772a90 commit 0a1bbbd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2477
-864
lines changed

build/ci/scripts/spec_with_pid.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ var color = Base.color;
2424

2525
exports = module.exports = Spec;
2626

27+
const prefix = process.env.VSC_PYTHON_CI_TEST_PARALLEL ? `${process.pid} ` : '';
28+
2729
/**
2830
* Constructs a new `Spec` reporter instance.
2931
*
@@ -51,7 +53,7 @@ function Spec(runner, options) {
5153

5254
runner.on(EVENT_SUITE_BEGIN, function (suite) {
5355
++indents;
54-
Base.consoleLog(color('suite', `${process.pid} %s%s`), indent(), suite.title);
56+
Base.consoleLog(color('suite', `${prefix}%s%s`), indent(), suite.title);
5557
});
5658

5759
runner.on(EVENT_SUITE_END, function () {
@@ -62,27 +64,27 @@ function Spec(runner, options) {
6264
});
6365

6466
runner.on(EVENT_TEST_PENDING, function (test) {
65-
var fmt = indent() + color('pending', `${process.pid} - %s`);
67+
var fmt = indent() + color('pending', `${prefix} %s`);
6668
Base.consoleLog(fmt, test.title);
6769
});
6870

6971
runner.on(EVENT_TEST_PASS, function (test) {
7072
var fmt;
7173
if (test.speed === 'fast') {
72-
fmt = indent() + color('checkmark', `${process.pid} ` + Base.symbols.ok) + color('pass', ' %s');
74+
fmt = indent() + color('checkmark', prefix + Base.symbols.ok) + color('pass', ' %s');
7375
Base.consoleLog(fmt, test.title);
7476
} else {
7577
fmt =
7678
indent() +
77-
color('checkmark', `${process.pid} ` + Base.symbols.ok) +
79+
color('checkmark', prefix + Base.symbols.ok) +
7880
color('pass', ' %s') +
7981
color(test.speed, ' (%dms)');
8082
Base.consoleLog(fmt, test.title, test.duration);
8183
}
8284
});
8385

8486
runner.on(EVENT_TEST_FAIL, function (test) {
85-
Base.consoleLog(indent() + color('fail', `${process.pid} %d) %s`), ++n, test.title);
87+
Base.consoleLog(indent() + color('fail', `${prefix}%d) %s`), ++n, test.title);
8688
});
8789

8890
runner.once(EVENT_RUN_END, self.epilogue.bind(self));

build/ci/templates/test_phases.yml

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -547,17 +547,9 @@ steps:
547547
- bash: |
548548
npm install -g vsce
549549
npm run clean
550-
npx tsc -p ./
551-
mkdir -p ./tmp/client/logging
552-
cp -r ./out/client/logging ./tmp/client
553-
npx gulp clean:cleanExceptTests
554-
cp -r ./out/test ./tmp/test
555550
npm run updateBuildNumber -- --buildNumber $BUILD_BUILDID
556551
npm run package
557-
npx gulp clean:cleanExceptTests
558-
mkdir -p ./out/client/logging
559-
cp -r ./tmp/client/logging ./out/client
560-
cp -r ./tmp/test ./out/test
552+
npx tsc -p ./
561553
node --no-force-async-hooks-checks ./out/test/smokeTest.js
562554
displayName: 'Run Smoke Tests'
563555
condition: and(succeeded(), contains(variables['TestsToRun'], 'testSmoke'))

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3474,7 +3474,7 @@
34743474
"test:functional:perf": "node --inspect-brk ./node_modules/mocha/bin/_mocha --require source-map-support/register --config ./build/.mocha.functional.perf.json",
34753475
"test:functional:memleak": "node --inspect-brk ./node_modules/mocha/bin/_mocha --require source-map-support/register --config ./build/.mocha.functional.json",
34763476
"test:functional:cover": "npm run test:functional",
3477-
"test:functional:parallel": "node ./build/ci/scripts/runFunctionalTests.js",
3477+
"test:functional:parallel": "cross-env VSC_PYTHON_CI_TEST_PARALLEL=1 node ./build/ci/scripts/runFunctionalTests.js",
34783478
"test:cover:report": "nyc --nycrc-path build/.nycrc report --reporter=text --reporter=html --reporter=text-summary --reporter=cobertura",
34793479
"testDebugger": "node ./out/test/testBootstrap.js ./out/test/debuggerTest.js",
34803480
"testSingleWorkspace": "node ./out/test/testBootstrap.js ./out/test/standardTest.js",

src/client/common/application/notebook.ts

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@ import type {
1616
} from 'vscode-proposed';
1717
import { UseProposedApi } from '../constants';
1818
import { IDisposableRegistry } from '../types';
19-
import {
20-
IApplicationEnvironment,
21-
IVSCodeNotebook,
22-
NotebookCellLanguageChangeEvent,
23-
NotebookCellOutputsChangeEvent,
24-
NotebookCellsChangeEvent
25-
} from './types';
19+
import { IApplicationEnvironment, IVSCodeNotebook, NotebookCellChangedEvent } from './types';
2620

2721
@injectable()
2822
export class VSCodeNotebook implements IVSCodeNotebook {
@@ -63,14 +57,10 @@ export class VSCodeNotebook implements IVSCodeNotebook {
6357
public get notebookEditors() {
6458
return this.canUseNotebookApi ? this.notebook.visibleNotebookEditors : [];
6559
}
66-
public get onDidChangeNotebookDocument(): Event<
67-
NotebookCellsChangeEvent | NotebookCellOutputsChangeEvent | NotebookCellLanguageChangeEvent
68-
> {
60+
public get onDidChangeNotebookDocument(): Event<NotebookCellChangedEvent> {
6961
return this.canUseNotebookApi
7062
? this._onDidChangeNotebookDocument.event
71-
: new EventEmitter<
72-
NotebookCellsChangeEvent | NotebookCellOutputsChangeEvent | NotebookCellLanguageChangeEvent
73-
>().event;
63+
: new EventEmitter<NotebookCellChangedEvent>().event;
7464
}
7565
public get activeNotebookEditor(): NotebookEditor | undefined {
7666
if (!this.useProposedApi) {
@@ -85,9 +75,7 @@ export class VSCodeNotebook implements IVSCodeNotebook {
8575
}
8676
return this._notebook!;
8777
}
88-
private readonly _onDidChangeNotebookDocument = new EventEmitter<
89-
NotebookCellsChangeEvent | NotebookCellOutputsChangeEvent | NotebookCellLanguageChangeEvent
90-
>();
78+
private readonly _onDidChangeNotebookDocument = new EventEmitter<NotebookCellChangedEvent>();
9179
private addedEventHandlers?: boolean;
9280
private _notebook?: typeof notebook;
9381
private readonly canUseNotebookApi?: boolean;
@@ -128,6 +116,12 @@ export class VSCodeNotebook implements IVSCodeNotebook {
128116
this.notebook.onDidChangeCellLanguage((e) =>
129117
this._onDidChangeNotebookDocument.fire({ ...e, type: 'changeCellLanguage' })
130118
),
119+
this.notebook.onDidChangeCellMetadata((e) =>
120+
this._onDidChangeNotebookDocument.fire({ ...e, type: 'changeCellMetadata' })
121+
),
122+
this.notebook.onDidChangeNotebookDocumentMetadata((e) =>
123+
this._onDidChangeNotebookDocument.fire({ ...e, type: 'changeNotebookMetadata' })
124+
),
131125
this.notebook.onDidChangeCellOutputs((e) =>
132126
this._onDidChangeNotebookDocument.fire({ ...e, type: 'changeCellOutputs' })
133127
),

src/client/common/application/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,13 @@ import {
6161
import type {
6262
NotebookCellLanguageChangeEvent as VSCNotebookCellLanguageChangeEvent,
6363
NotebookCellMetadata,
64+
NotebookCellMetadataChangeEvent as VSCNotebookCellMetadataChangeEvent,
6465
NotebookCellOutputsChangeEvent as VSCNotebookCellOutputsChangeEvent,
6566
NotebookCellsChangeEvent as VSCNotebookCellsChangeEvent,
6667
NotebookContentProvider,
6768
NotebookDocument,
6869
NotebookDocumentFilter,
70+
NotebookDocumentMetadataChangeEvent as VSCNotebookDocumentMetadataChangeEvent,
6971
NotebookEditor,
7072
NotebookKernel,
7173
NotebookKernelProvider
@@ -1526,10 +1528,16 @@ export interface IClipboard {
15261528

15271529
export type NotebookCellsChangeEvent = { type: 'changeCells' } & VSCNotebookCellsChangeEvent;
15281530
export type NotebookCellOutputsChangeEvent = { type: 'changeCellOutputs' } & VSCNotebookCellOutputsChangeEvent;
1531+
export type NotebookCellMetadataChangeEvent = { type: 'changeCellMetadata' } & VSCNotebookCellMetadataChangeEvent;
15291532
export type NotebookCellLanguageChangeEvent = { type: 'changeCellLanguage' } & VSCNotebookCellLanguageChangeEvent;
1533+
export type NotebookDocumentMetadataChangeEvent = {
1534+
type: 'changeNotebookMetadata';
1535+
} & VSCNotebookDocumentMetadataChangeEvent;
15301536
export type NotebookCellChangedEvent =
15311537
| NotebookCellsChangeEvent
15321538
| NotebookCellOutputsChangeEvent
1539+
| NotebookCellMetadataChangeEvent
1540+
| NotebookDocumentMetadataChangeEvent
15331541
| NotebookCellLanguageChangeEvent;
15341542
export const IVSCodeNotebook = Symbol('IVSCodeNotebook');
15351543
export interface IVSCodeNotebook {

src/client/datascience/context/activeEditorContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ export class ActiveEditorContextService implements IExtensionSingleActivationSer
100100
return;
101101
}
102102
this.hasNativeNotebookCells
103-
.set((this.notebookEditorProvider.activeEditor?.model?.cells?.length || 0) > 0)
103+
.set((this.notebookEditorProvider.activeEditor?.model?.cellCount || 0) > 0)
104104
.ignoreErrors();
105105
}
106106
private onDidChangeActiveInteractiveWindow(e?: IInteractiveWindow) {

src/client/datascience/export/exportUtil.ts

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { inject, injectable } from 'inversify';
33
import * as os from 'os';
44
import * as path from 'path';
55
import * as uuid from 'uuid/v4';
6-
import { CancellationTokenSource, Uri } from 'vscode';
6+
import { Uri } from 'vscode';
77
import { TemporaryDirectory } from '../../common/platform/types';
88
import { sleep } from '../../common/utils/async';
99
import { ICell, IDataScienceFileSystem, INotebookExporter, INotebookModel, INotebookStorage } from '../types';
@@ -68,24 +68,15 @@ export class ExportUtil {
6868

6969
public async removeSvgs(source: Uri) {
7070
const model = await this.notebookStorage.getOrCreateModel({ file: source });
71-
72-
const newCells: ICell[] = [];
73-
for (const cell of model.cells) {
74-
const outputs = cell.data.outputs;
75-
if (outputs as nbformat.IOutput[]) {
76-
this.removeSvgFromOutputs(outputs as nbformat.IOutput[]);
71+
const content = JSON.parse(model.getContent()) as nbformat.INotebookContent;
72+
for (const cell of content.cells) {
73+
const outputs = cell.outputs as nbformat.IOutput[];
74+
if (Array.isArray(outputs)) {
75+
this.removeSvgFromOutputs(outputs);
7776
}
78-
newCells.push(cell);
7977
}
80-
model.update({
81-
kind: 'modify',
82-
newCells: newCells,
83-
oldCells: model.cells as ICell[],
84-
oldDirty: false,
85-
newDirty: false,
86-
source: 'user'
87-
});
88-
await this.notebookStorage.save(model, new CancellationTokenSource().token);
78+
await this.fs.writeFile(source, JSON.stringify(content, undefined, model.indentAmount));
79+
model.dispose(); // We're modifying the JSON in file manually, hence blow away cached model.
8980
}
9081

9182
private removeSvgFromOutputs(outputs: nbformat.IOutput[]) {

src/client/datascience/interactive-ipynb/nativeEditor.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ import {
6767
INotebookExtensibility,
6868
INotebookImporter,
6969
INotebookMetadataLive,
70-
INotebookModel,
7170
INotebookProvider,
7271
IStatusProvider,
7372
IThemeFinder,
@@ -87,6 +86,7 @@ import { IDataViewerFactory } from '../data-viewing/types';
8786
import { getCellHashProvider } from '../editor-integration/cellhashprovider';
8887
import { KernelSelector } from '../jupyter/kernels/kernelSelector';
8988
import { KernelConnectionMetadata } from '../jupyter/kernels/types';
89+
import { NativeEditorNotebookModel } from '../notebookStorage/notebookModel';
9090

9191
const nativeEditorDir = path.join(EXTENSION_ROOT_DIR, 'out', 'datascience-ui', 'notebook');
9292
export class NativeEditor extends InteractiveBase implements INotebookEditor {
@@ -134,7 +134,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
134134
public get isDirty(): boolean {
135135
return this.model ? this.model.isDirty : false;
136136
}
137-
public get model(): Readonly<INotebookModel> {
137+
public get model(): Readonly<NativeEditorNotebookModel> {
138138
return this._model;
139139
}
140140
public readonly type: 'old' | 'custom' = 'custom';
@@ -181,7 +181,7 @@ export class NativeEditor extends InteractiveBase implements INotebookEditor {
181181
notebookProvider: INotebookProvider,
182182
useCustomEditorApi: boolean,
183183
private trustService: ITrustService,
184-
private _model: INotebookModel,
184+
private _model: NativeEditorNotebookModel,
185185
webviewPanel: WebviewPanel | undefined,
186186
selector: KernelSelector,
187187
private nbExtensibility: INotebookExtensibility

src/client/datascience/interactive-ipynb/nativeEditorOldWebView.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { Commands, Telemetry } from '../constants';
2929
import { IDataViewerFactory } from '../data-viewing/types';
3030
import { InteractiveWindowMessages } from '../interactive-common/interactiveWindowTypes';
3131
import { KernelSelector } from '../jupyter/kernels/kernelSelector';
32+
import { NativeEditorNotebookModel } from '../notebookStorage/notebookModel';
3233
import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider';
3334
import {
3435
ICodeCssGenerator,
@@ -42,7 +43,6 @@ import {
4243
INotebookExporter,
4344
INotebookExtensibility,
4445
INotebookImporter,
45-
INotebookModel,
4646
INotebookProvider,
4747
IStatusProvider,
4848
IThemeFinder,
@@ -100,7 +100,7 @@ export class NativeEditorOldWebView extends NativeEditor {
100100
useCustomEditorApi: boolean,
101101
private readonly storage: INotebookStorageProvider,
102102
trustService: ITrustService,
103-
model: INotebookModel,
103+
model: NativeEditorNotebookModel,
104104
webviewPanel: WebviewPanel | undefined,
105105
selector: KernelSelector,
106106
notebookExtensibility: INotebookExtensibility

src/client/datascience/interactive-ipynb/nativeEditorProviderOld.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { IDataViewerFactory } from '../data-viewing/types';
3434
import { NotebookModelChange } from '../interactive-common/interactiveWindowTypes';
3535
import { KernelSelector } from '../jupyter/kernels/kernelSelector';
3636
import { NativeEditorProvider } from '../notebookStorage/nativeEditorProvider';
37+
import { NativeEditorNotebookModel } from '../notebookStorage/notebookModel';
3738
import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider';
3839
import { VSCodeNotebookModel } from '../notebookStorage/vscNotebookModel';
3940
import {
@@ -193,7 +194,7 @@ export class NativeEditorProviderOld extends NativeEditorProvider {
193194
}
194195
}
195196

196-
protected createNotebookEditor(model: INotebookModel, panel?: WebviewPanel): NativeEditor {
197+
protected createNotebookEditor(model: NativeEditorNotebookModel, panel?: WebviewPanel): NativeEditor {
197198
const editor = new NativeEditorOldWebView(
198199
this.serviceContainer.getAll<IInteractiveWindowListener>(IInteractiveWindowListener),
199200
this.serviceContainer.get<ILiveShareApi>(ILiveShareApi),

src/client/datascience/interactive-ipynb/trustCommandHandler.ts

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,20 @@
66
import { inject, injectable } from 'inversify';
77
import { commands, Uri } from 'vscode';
88
import { IExtensionSingleActivationService } from '../../activation/types';
9-
import { IApplicationShell, ICommandManager } from '../../common/application/types';
9+
import { IApplicationShell, ICommandManager, IVSCodeNotebook } from '../../common/application/types';
1010
import { ContextKey } from '../../common/contextKey';
1111
import '../../common/extensions';
12+
import { IFileSystem } from '../../common/platform/types';
1213
import { IDisposableRegistry } from '../../common/types';
14+
import { createDeferred } from '../../common/utils/async';
1315
import { swallowExceptions } from '../../common/utils/decorators';
1416
import { DataScience } from '../../common/utils/localize';
17+
import { noop } from '../../common/utils/misc';
1518
import { sendTelemetryEvent } from '../../telemetry';
1619
import { Commands, Telemetry } from '../constants';
1720
import { INotebookStorageProvider } from '../notebookStorage/notebookStorageProvider';
18-
import { INotebookEditorProvider, ITrustService } from '../types';
21+
import { VSCodeNotebookModel } from '../notebookStorage/vscNotebookModel';
22+
import { INotebookEditorProvider, INotebookModel, ITrustService } from '../types';
1923

2024
@injectable()
2125
export class TrustCommandHandler implements IExtensionSingleActivationService {
@@ -25,7 +29,9 @@ export class TrustCommandHandler implements IExtensionSingleActivationService {
2529
@inject(INotebookStorageProvider) private readonly storageProvider: INotebookStorageProvider,
2630
@inject(ICommandManager) private readonly commandManager: ICommandManager,
2731
@inject(IApplicationShell) private readonly applicationShell: IApplicationShell,
28-
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry
32+
@inject(IDisposableRegistry) private readonly disposables: IDisposableRegistry,
33+
@inject(IVSCodeNotebook) private readonly vscNotebook: IVSCodeNotebook,
34+
@inject(IFileSystem) private readonly fs: IFileSystem
2935
) {}
3036
public async activate(): Promise<void> {
3137
this.activateInBackground().ignoreErrors();
@@ -61,11 +67,11 @@ export class TrustCommandHandler implements IExtensionSingleActivationService {
6167
sendTelemetryEvent(Telemetry.TrustAllNotebooks);
6268
break;
6369
case DataScience.trustNotebook():
64-
// Update model trust
65-
model.trust();
66-
const contents = model.getContent();
67-
await this.trustService.trustNotebook(model.file, contents);
68-
sendTelemetryEvent(Telemetry.TrustNotebook);
70+
if (model instanceof VSCodeNotebookModel) {
71+
await this.trustNativeNotebook(model);
72+
} else {
73+
await this.trustNotebook(model);
74+
}
6975
break;
7076
case DataScience.doNotTrustNotebook():
7177
sendTelemetryEvent(Telemetry.DoNotTrustNotebook);
@@ -74,4 +80,52 @@ export class TrustCommandHandler implements IExtensionSingleActivationService {
7480
break;
7581
}
7682
}
83+
private async trustNotebook(model: INotebookModel) {
84+
// Update model trust
85+
model.trust();
86+
const contents = model.getContent();
87+
await this.trustService.trustNotebook(model.file, contents);
88+
sendTelemetryEvent(Telemetry.TrustNotebook);
89+
}
90+
private async trustNativeNotebook(model: VSCodeNotebookModel) {
91+
const trustedNotebookInTrustService = createDeferred<void>();
92+
const doc = this.vscNotebook.notebookDocuments.find((item) =>
93+
this.fs.arePathsSame(item.uri.fsPath, model.file.fsPath)
94+
);
95+
let fileReverted = false;
96+
const disposable = this.vscNotebook.onDidChangeNotebookDocument((e) => {
97+
if (e.document !== doc) {
98+
return;
99+
}
100+
trustedNotebookInTrustService.promise
101+
.then(() => {
102+
// Notebook has been trusted, revert the changes in the document so that we re-load the cells.
103+
// This is a hacky solution for trusting native notebooks.
104+
if (!fileReverted) {
105+
fileReverted = true;
106+
return commands.executeCommand('workbench.action.files.revert');
107+
}
108+
})
109+
.catch(noop);
110+
disposable.dispose();
111+
});
112+
this.disposables.push(disposable);
113+
114+
try {
115+
// Update model trust
116+
await model.trustNotebook();
117+
// Trust the original contents & contents generated vy using the VSC Notebook model.
118+
// When generating JSON from VSC, sometimes contents can get auto formatted based on user settings (hence that JSON in ipynb could be different from original contents).
119+
// Thus trust both, the original content & the new content generated by VSC.
120+
const originalContents = model.getOriginalContentOnDisc();
121+
const contents = model.getContent();
122+
await Promise.all([
123+
this.trustService.trustNotebook(model.file, contents),
124+
this.trustService.trustNotebook(model.file, originalContents)
125+
]);
126+
sendTelemetryEvent(Telemetry.TrustNotebook);
127+
} finally {
128+
trustedNotebookInTrustService.resolve();
129+
}
130+
}
77131
}

0 commit comments

Comments
 (0)