Bugfix: Gather cell outputs with gathered cells

This commit is contained in:
Andrew Head 2019-02-27 09:00:48 -08:00
Родитель 40f356fa1a
Коммит 1f067987da
17 изменённых файлов: 134 добавлений и 254 удалений

1
.gitignore поставляемый
Просмотреть файл

@ -19,6 +19,7 @@ python3.js
# Build files
dist/
lib/
gather-*.tgz
# Notebooks
notebooks/

Просмотреть файл

@ -93,18 +93,20 @@ function _loadExecutionFromJson(executionJson: JSONObject): CellExecution {
let persistentId = _getString(cellJson, 'persistentId');
let executionCount = _getNumber(cellJson, 'executionCount');
let hasError = _getBoolean(cellJson, 'hasError');
let isCode = _getBoolean(cellJson, 'isCode');
let text = _getString(cellJson, 'text');
let executionTimeString = _getString(executionJson, "executionTime");
let executionTime = new Date(executionTimeString);
if (id == null || executionCount == null || hasError == null ||
isCode == null || text == null || executionTime == null) {
text == null || executionTime == null) {
log("Cell could not be loaded, as it's missing a critical field.");
return null;
}
let cell = new SimpleCell(id, persistentId, executionCount, hasError, isCode, text);
/**
* TODO(andrewhead): Update with Kunal's code for serializing and deserializing outputs.
*/
let cell = new SimpleCell(id, executionCount, hasError, text, [], persistentId);
return new CellExecution(cell, executionTime);
}

Просмотреть файл

@ -34,7 +34,6 @@ export function storeHistory(notebookModel: INotebookModel, executionLog: Execut
cellJson.persistentId = cell.persistentId;
cellJson.executionCount = cell.executionCount;
cellJson.hasError = cell.hasError;
cellJson.isCode = cell.isCode;
cellJson.text = cell.text;
let cellExecutionJson = new Object(null) as CellExecutionJson;

Просмотреть файл

@ -106,7 +106,7 @@ export class NotebookOpener {
this._notebooks = notebooks;
}
openNotebookForSlice(slice: SlicedExecution) {
openNotebookForSlice(slice: SlicedExecution, outputSelections?: OutputSelection[]) {
/*
* TODO(andrewhead): give the document a context-sensitive name, say the name of the result.
*/
@ -118,7 +118,7 @@ export class NotebookOpener {
let notebookJson = notebookModel.toJSON() as nbformat.INotebookContent;
notebookJson.cells = []
if (slice) {
let cellsJson = getCellsJsonForSlice(slice, []);
let cellsJson = getCellsJsonForSlice(slice, outputSelections);
for (let cell of cellsJson) {
notebookJson.cells.push(cell);
}
@ -148,7 +148,7 @@ function getCellsJsonForSlice(slice: SlicedExecution, outputSelections?: OutputS
slicedCell = slicedCell.copy();
slicedCell.text = cellSlice.textSliceLines;
}
let cellJson = slicedCell.toJupyterJSON();
let cellJson = slicedCell.serialize();
// This new cell hasn't been executed yet. So don't mark it as having been executed.
cellJson.execution_count = null;
// Add a flag to distinguish gathered cells from other cells.

Просмотреть файл

@ -1,7 +1,8 @@
import { AbstractOutputterCell } from "../packages/cell";
import { ICodeCellModel, CodeCellModel } from "@jupyterlab/cells";
import { IOutputModel } from "@jupyterlab/rendermime";
import { UUID } from "@phosphor/coreutils";
import { nbformat } from "@jupyterlab/coreutils";
import { AbstractCell } from "../packages/cell";
/**
* Create a new cell with the same ID and content.
@ -13,7 +14,7 @@ export function copyICodeCellModel(cell: ICodeCellModel): ICodeCellModel {
/**
* Implementation of SliceableCell for Jupyter Lab. Wrapper around the ICodeCellModel.
*/
export class LabCell extends AbstractOutputterCell<IOutputModel[]> {
export class LabCell extends AbstractCell {
constructor(model: ICodeCellModel) {
super();
@ -69,6 +70,10 @@ export class LabCell extends AbstractOutputterCell<IOutputModel[]> {
}
}
get outputs(): nbformat.IOutput[] {
return this.output.map((output) => output.toJSON());
}
get gathered(): boolean {
return this._model.metadata.get("gathered") as boolean;
}
@ -78,7 +83,7 @@ export class LabCell extends AbstractOutputterCell<IOutputModel[]> {
return new LabCell(clonedModel);
}
toJupyterJSON(): any {
serialize(): any {
return this._model.toJSON();
}

Просмотреть файл

@ -1,27 +1,50 @@
import { LocationSet } from "../../slicing/Slice";
import { nbformat } from "@jupyterlab/coreutils";
import { UUID } from "@phosphor/coreutils";
/**
* Generic interface for accessing cell data.
* Generic interface for accessing data about a code cell.
*/
export interface ICell {
is_cell: boolean;
id: string;
/**
* TODO(andrewhead): make sure that when a cell is copied, it doesn't have the same
* persistent ID.
* The ID assigned to a cell by Jupyter Lab. This ID may change each time the notebook is open,
* due to the implementation of Jupyter Lab.
*/
persistentId: string;
readonly id: string;
/**
* A persistent ID for this cell, that will stay the same even as the notebook is closed and
* re-opened. In general, all gathering functionality should refer to cells using this ID.
*/
readonly persistentId: string;
/**
* Whether this cell was created by gathering code.
*/
gathered: boolean;
executionCount: number;
hasError: boolean;
isCode: boolean;
text: string;
gathered: boolean;
copy: () => ICell; // deep copy if holding a model.
outputs: nbformat.IOutput[];
/**
* Produce JSON that can be read to make a new Jupyter notebook cell.
* TODO(andrewhead): return JSON with cell JSON static type.
* Flag used for type checking.
*/
toJupyterJSON: () => any;
readonly is_cell: boolean;
/**
* Create a deep copy of the cell. Copies will have all the same properties, except for the
* persistent ID, which should be entirely new.
*/
copy: () => ICell;
/**
* Serialize this ICell to JSON that can be stored in a notebook file, or which can be used to
* create a new Jupyter cell.
*/
serialize: () => nbformat.ICodeCell;
}
/**
@ -37,10 +60,12 @@ export abstract class AbstractCell implements ICell {
abstract isCode: boolean;
abstract text: string;
abstract gathered: boolean;
abstract outputs: nbformat.IOutput[];
abstract copy(): AbstractCell;
/**
* Output descriptive (unsensitive) data about this cell. No code!
* This method is called by the logger to sanitize cell data before logging it. This method
* should elide any sensitive data, like the cell's text.
*/
toJSON(): any {
return {
@ -54,12 +79,13 @@ export abstract class AbstractCell implements ICell {
};
}
toJupyterJSON(): any {
serialize(): nbformat.ICodeCell {
return {
id: this.id,
execution_count: this.executionCount,
source: this.text,
cell_type: "code",
outputs: this.outputs,
metadata: {
gathered: this.gathered,
persistent_id: this.persistentId,
@ -70,62 +96,38 @@ export abstract class AbstractCell implements ICell {
export class SimpleCell extends AbstractCell {
constructor(id: string, persistentId: string, executionCount: number,
hasError: boolean, isCode: boolean, text: string) {
constructor(id: string, executionCount: number,
hasError: boolean, text: string, outputs: nbformat.IOutput[], persistentId?: string) {
super();
this.is_cell = true;
this.id = id;
this.persistentId = persistentId;
this.persistentId = persistentId ? persistentId : UUID.uuid4.toString();
this.executionCount = executionCount;
this.hasError = hasError;
this.isCode = isCode;
this.text = text;
this.outputs = outputs;
this.gathered = false;
}
copy(): SimpleCell {
return new SimpleCell(this.id, this.persistentId, this.executionCount, this.hasError, this.isCode, this.text);
return new SimpleCell(this.id, this.executionCount, this.hasError, this.text, this.outputs);
}
public readonly is_cell: boolean;
public readonly id: string;
public readonly persistentId: string;
public readonly executionCount: number;
public readonly hasError: boolean;
public readonly isCode: boolean;
public readonly text: string;
public readonly gathered: boolean;
readonly is_cell: boolean;
readonly id: string;
readonly persistentId: string;
readonly executionCount: number;
readonly hasError: boolean;
readonly isCode: boolean;
readonly text: string;
readonly outputs: nbformat.IOutput[];
readonly gathered: boolean;
}
export function instanceOfICell(object: any): object is ICell {
return object && (typeof object == "object") && "is_cell" in object;
}
/**
* Cell interface with output data.
*/
export interface IOutputterCell<TOutputModel> extends ICell {
is_outputter_cell: boolean;
output: TOutputModel;
}
/**
* Type checker for IOutputterCell.
*/
export function instanceOfIOutputterCell<TOutputModel>(object: any): object is IOutputterCell<TOutputModel> {
return object && (typeof object == "object") && "is_outputter_cell" in object;
}
/**
* Abstract class for a cell with output data.
*/
export abstract class AbstractOutputterCell<TOutputModel>
extends AbstractCell implements IOutputterCell<TOutputModel> {
readonly is_outputter_cell: boolean = true;
abstract output: TOutputModel;
}
/**
* A slice of a cell.
*/

Просмотреть файл

@ -39,7 +39,7 @@ export class GatherController implements IGatherObserver {
} else if (newState == GatherState.GATHER_TO_NOTEBOOK) {
log("Gathering to notebook", { slice: mergedSlice });
if (this._notebookOpener !== undefined) {
this._notebookOpener.openNotebookForSlice(mergedSlice);
this._notebookOpener.openNotebookForSlice(mergedSlice, [...model.selectedOutputs]);
model.resetChosenSlices();
}
} else if (newState == GatherState.GATHER_TO_SCRIPT) {

Просмотреть файл

@ -3,20 +3,21 @@ import { RevisionModel } from '../revision/model';
import { CodeVersionModel } from '../codeversion/model';
import { SlicedCellModel } from '../slicedcell/model';
import { SlicedExecution } from '../../slicing/ExecutionSlicer';
import { ICell, IOutputterCell, instanceOfIOutputterCell, CellSlice } from '../cell/model';
import { ICell, CellSlice } from '../cell/model';
import { computeTextDiff } from './diff';
import { GatherModel } from '../gather';
import { nbformat } from '@jupyterlab/coreutils';
/**
* Build a history model of how a cell was computed across notebook snapshots.
*/
export function buildHistoryModel<TOutputModel>(
export function buildHistoryModel(
gatherModel: GatherModel,
selectedCellPersistentId: string,
executionVersions: SlicedExecution[],
includeOutput?: boolean
): HistoryModel<TOutputModel> {
): HistoryModel {
// All cells in past revisions will be compared to those in the current revision. For the most
// recent version, save a mapping from cells' IDs to their content, so we can look them up to
@ -28,7 +29,7 @@ export function buildHistoryModel<TOutputModel>(
});
// Compute diffs between each of the previous revisions and the current revision.
let revisions = new Array<RevisionModel<TOutputModel>>();
let revisions = new Array<RevisionModel>();
executionVersions.forEach(function (executionVersion, versionIndex) {
// Then difference the code in each cell.
@ -56,7 +57,7 @@ export function buildHistoryModel<TOutputModel>(
slicedCellModels.push(slicedCell);
})
let output: TOutputModel = null;
let output: nbformat.IOutput[] = null;
if (includeOutput) {
let selectedCell: ICell = null;
executionVersion.cellSlices.map(cs => cs.cell).forEach(function (cellModel) {
@ -64,11 +65,8 @@ export function buildHistoryModel<TOutputModel>(
selectedCell = cellModel;
}
});
if (selectedCell && instanceOfIOutputterCell(selectedCell)) {
let selectedOutputterCell = selectedCell as IOutputterCell<TOutputModel>;
if (selectedCell.output) {
output = selectedOutputterCell.output;
}
if (selectedCell && selectedCell.outputs) {
output = selectedCell.outputs;
}
}
@ -77,7 +75,7 @@ export function buildHistoryModel<TOutputModel>(
cells: slicedCellModels,
isLatest: isLatestVersion
});
let revisionModel = new RevisionModel<TOutputModel>({
let revisionModel = new RevisionModel({
versionIndex: versionIndex + 1, // Version index should start at 1
source: codeVersionModel,
slice: executionVersion,

Просмотреть файл

@ -3,29 +3,29 @@ import { IRevisionModel } from "../revision";
/**
* The definition of a model object for a code history.
*/
export interface IHistoryModel<TOutputModel> {
readonly revisions: ReadonlyArray<IRevisionModel<TOutputModel>>;
export interface IHistoryModel {
readonly revisions: ReadonlyArray<IRevisionModel>;
}
/**
* An implementation of the history model.
*/
export class HistoryModel<TOutputModel> implements IHistoryModel<TOutputModel> {
export class HistoryModel implements IHistoryModel {
/**
* Construct a history model
*/
constructor(options: HistoryModel.IOptions<TOutputModel>) {
constructor(options: HistoryModel.IOptions) {
this._revisions = options.revisions;
}
/**
* Get the versions from the history.
*/
get revisions(): ReadonlyArray<IRevisionModel<TOutputModel>> {
get revisions(): ReadonlyArray<IRevisionModel> {
return this._revisions;
}
private _revisions: Array<IRevisionModel<TOutputModel>> = null;
private _revisions: Array<IRevisionModel> = null;
}
/**
@ -35,10 +35,10 @@ export namespace HistoryModel {
/**
* The options used to initialize a `HistoryModel`.
*/
export interface IOptions<TOutputModel> {
export interface IOptions {
/**
* Versions of the code.
*/
revisions?: Array<IRevisionModel<TOutputModel>>;
revisions?: Array<IRevisionModel>;
}
}

Просмотреть файл

@ -19,12 +19,12 @@ const OLDER_VERSIONS_CONTENTS_CLASS = 'jp-HistoryViewer-olderversions-revisions'
/**
* A widget for showing the history of a result and how it was produced.
*/
export class HistoryViewer<TOutputModel> extends Widget {
export class HistoryViewer extends Widget {
/**
* Construct a new history viewer.
*/
constructor(options: HistoryViewer.IOptions<TOutputModel>) {
constructor(options: HistoryViewer.IOptions) {
super();
this.addClass(HISTORY_VIEWER_CLASS);
@ -74,7 +74,7 @@ export class HistoryViewer<TOutputModel> extends Widget {
/**
* Get the model used by the history viewer.
*/
get model(): IHistoryModel<TOutputModel> {
get model(): IHistoryModel {
return this._model;
}
@ -94,7 +94,7 @@ export class HistoryViewer<TOutputModel> extends Widget {
super.dispose();
}
private _model: IHistoryModel<TOutputModel> = null;
private _model: IHistoryModel = null;
}
@ -105,15 +105,15 @@ export namespace HistoryViewer {
/**
* An options object for initializing a history viewer widget.
*/
export interface IOptions<TOutputModel> {
export interface IOptions {
/**
* The model used by the history viewer.
*/
model: IHistoryModel<TOutputModel>;
model: IHistoryModel;
/**
* The renderer for output models.
*/
outputRenderer: IOutputRenderer<TOutputModel>;
outputRenderer: IOutputRenderer;
}
}

Просмотреть файл

@ -1,11 +1,12 @@
import { ICodeVersionModel } from '../codeversion/model';
import { SlicedExecution } from '../../slicing/ExecutionSlicer';
import { GatherModel } from '../gather';
import { nbformat } from '@jupyterlab/coreutils';
/**
* The definition of a model object for a code version.
*/
export interface IRevisionModel<TOutputModel> {
export interface IRevisionModel {
/**
* A unique index for this code version---lower indexes were are for earlier versions.
*/
@ -29,7 +30,7 @@ export interface IRevisionModel<TOutputModel> {
/**
* The result of the computation.
*/
readonly output: TOutputModel;
readonly output: nbformat.IOutput[];
/**
* Whether this revision is the latest revision.
@ -45,11 +46,11 @@ export interface IRevisionModel<TOutputModel> {
/**
* An implementation of the code version model.
*/
export class RevisionModel<TOutputModel> implements IRevisionModel<TOutputModel> {
export class RevisionModel implements IRevisionModel {
/**
* Construct a code version model.
*/
constructor(options: RevisionModel.IOptions<TOutputModel>) {
constructor(options: RevisionModel.IOptions) {
this.versionIndex = options.versionIndex;
this._source = options.source;
this._slice = options.slice;
@ -86,7 +87,7 @@ export class RevisionModel<TOutputModel> implements IRevisionModel<TOutputModel>
/**
* Get the result of this computation.
*/
get output(): TOutputModel {
get output(): nbformat.IOutput[] {
return this._output;
}
@ -100,7 +101,7 @@ export class RevisionModel<TOutputModel> implements IRevisionModel<TOutputModel>
private _source: ICodeVersionModel;
private _slice: SlicedExecution;
private _gatherModel: GatherModel;
private _output: TOutputModel;
private _output: nbformat.IOutput[];
private _timeCreated: Date;
}
@ -111,7 +112,7 @@ export namespace RevisionModel {
/**
* The options used to initialize a `CodeVerionModel`.
*/
export interface IOptions<TOutputModel> {
export interface IOptions {
/**
* A slice of the source code for this revision.
*/
@ -135,7 +136,7 @@ export namespace RevisionModel {
/**
* The display data for the result at this version.
*/
output?: TOutputModel;
output?: nbformat.IOutput[];
/**
* Whether this revision is the latest revision.

Просмотреть файл

@ -5,6 +5,7 @@ import { CodeVersion } from '../codeversion';
import { GatherState } from '../gather';
import { log } from '../../utils/log';
import { getRelativeTime } from '../../utils/date';
import { nbformat } from '@jupyterlab/coreutils';
// HTML element classes for rendered revisions
@ -17,25 +18,25 @@ const REVISION_BUTTON_CLASS = 'jp-Revision-button';
const REVISION_CELLS_CLASS = 'jp-Revision-cells';
export interface IOutputRenderer<TOutputModel> {
render(outputModel: TOutputModel): HTMLElement;
export interface IOutputRenderer {
render(outputModel: nbformat.IOutput): HTMLElement;
}
export namespace Revision {
export interface IOptions<TOutputModel> {
model: IRevisionModel<TOutputModel>;
outputRenderer: IOutputRenderer<TOutputModel>;
export interface IOptions {
model: IRevisionModel;
outputRenderer: IOutputRenderer;
now: Date; // the current time, which should be the same for all revisions
}
}
export class Revision<TOutputModel> extends Widget {
export class Revision extends Widget {
readonly model: IRevisionModel<TOutputModel>;
readonly model: IRevisionModel;
constructor(options: Revision.IOptions<TOutputModel>) {
constructor(options: Revision.IOptions) {
super();
this.addClass(REVISION_CLASS);
let model = (this.model = options.model);
@ -74,8 +75,8 @@ export class Revision<TOutputModel> extends Widget {
model: model.source,
}));
if (model.output) {
let outputElement = outputRenderer.render(model.output);
if (model.output && model.output.length >= 1) {
let outputElement = outputRenderer.render(model.output[0]);
if (outputElement) {
cellsLayout.addWidget(new Widget({
node: outputElement

Просмотреть файл

@ -1,7 +1,6 @@
import { NumberSet, range } from "./Set";
import { ControlFlowGraph } from "./ControlFlowAnalysis";
import { ILocation, parse, IModule } from "../parsers/python/python_parser";
import { ICell } from "../packages/cell";
import { Set } from "./Set";
import { DataflowAnalyzer } from "./DataflowAnalysis";
@ -120,104 +119,4 @@ export function sliceLines(code: string, relevantLineNumbers: NumberSet) {
} while (relevantLineNumbers.size > lastSize);
return relevantLineNumbers;
}
export class OldCellProgram<CellType extends ICell> {
private code: string;
private changedCellLineNumbers: [number, number];
private cellByLine: CellType[] = [];
private lineRangeForCell: { [id: string]: [number, number] } = {};
constructor(changedCell: CellType, private cells: CellType[], selection?: [number, number]) {
this.code = '';
let lineNumber = 1;
for (let i = 0; i < cells.length; i++) {
const cell = cells[i];
if (cell.isCode) continue;
const cellText = cell.text;
this.code += cellText + '\n';
const lineCount = cellText.split('\n').length;
this.lineRangeForCell[cell.id] = [lineNumber, lineNumber + lineCount];
for (let lc = 0; lc < lineCount; lc++) {
this.cellByLine[lc + lineNumber] = cell;
}
if (cell.id === changedCell.id) {
this.changedCellLineNumbers = selection ?
[lineNumber + selection[0], lineNumber + selection[1]] :
[lineNumber, lineNumber + lineCount - 1];
}
lineNumber += lineCount;
}
}
private followDataflow(direction: DataflowDirection): NumberSet {
const ast = parse(this.code);
const cfg = new ControlFlowGraph(ast);
let dataflowAnalyzer = new DataflowAnalyzer();
const dfa = dataflowAnalyzer.analyze(cfg).flows;
dfa.add(...cfg.getControlDependencies());
const forwardDirection = direction === DataflowDirection.Forward;
let relevantLineNumbers = new NumberSet();
const [startLine, endLine] = this.changedCellLineNumbers;
for (let line = startLine; line <= endLine; line++) {
relevantLineNumbers.add(line);
}
let lastSize: number;
do {
lastSize = relevantLineNumbers.size;
for (let flow of dfa.items) {
const fromLines = lineRange(flow.fromNode.location);
const toLines = lineRange(flow.toNode.location);
const startLines = forwardDirection ? fromLines : toLines;
const endLines = forwardDirection ? toLines : fromLines;
if (!relevantLineNumbers.intersect(startLines).empty) {
relevantLineNumbers = relevantLineNumbers.union(endLines);
}
}
} while (relevantLineNumbers.size > lastSize);
return relevantLineNumbers;
}
public getDataflowCells(direction: DataflowDirection): Array<[CellType, NumberSet]> {
const relevantLineNumbers = this.followDataflow(direction);
const cellsById: { [id: string]: CellType } = {};
const cellExecutionInfo: { [id: string]: NumberSet } = {};
for (let line of relevantLineNumbers.items.sort((line1, line2) => line1 - line2)) {
let cellModel = this.cellByLine[line];
let lineNumbers;
if (!cellExecutionInfo.hasOwnProperty(cellModel.id)) {
lineNumbers = new NumberSet();
cellsById[cellModel.id] = cellModel;
cellExecutionInfo[cellModel.id] = lineNumbers;
}
lineNumbers = cellExecutionInfo[cellModel.id];
lineNumbers.add(line - this.lineRangeForCell[cellModel.id][0]);
}
let result = new Array<[CellType, NumberSet]>();
for (let cellId in cellExecutionInfo) {
result.push([cellsById[cellId], cellExecutionInfo[cellId]]);
}
return result;
}
public getDataflowText(direction: DataflowDirection): string {
const relevantLineNumbers = this.followDataflow(direction);
let text = '';
let lineNumber = 0;
for (let i = 0; i < this.cells.length; i++) {
const cell = this.cells[i];
if (cell.isCode) continue;
const cellLines = cell.text.split('\n');
for (let line = 0; line < cellLines.length; line++) {
if (relevantLineNumbers.contains(line + lineNumber + 1)) {
text += cellLines[line] + '\n';
}
}
lineNumber += cellLines.length;
}
return text;
}
}

Просмотреть файл

@ -18,10 +18,10 @@ describe('CellSlice', () => {
].join("\n"),
hasError: false,
executionCount: 1,
isCode: true,
outputs: [],
gathered: false,
copy: () => null,
toJupyterJSON: () => null
serialize: () => null
}, new LocationSet(
{ first_line: 1, first_column: 0, last_line: 1, last_column: 5 },
{ first_line: 2, first_column: 4, last_line: 3, last_column: 4 }
@ -47,10 +47,10 @@ describe('CellSlice', () => {
].join("\n"),
hasError: false,
executionCount: 1,
isCode: true,
outputs: [],
gathered: false,
copy: () => null,
toJupyterJSON: () => null
serialize: () => null
}, new LocationSet(
{ first_line: 1, first_column: 0, last_line: 1, last_column: 5 },
{ first_line: 2, first_column: 4, last_line: 3, last_column: 4 }

Просмотреть файл

@ -6,19 +6,18 @@ import { ICell, CellSlice } from "../packages/cell";
describe('SlicedExecution', () => {
function cell(id: string, executionCount: number, ...codeLines: string[]): ICell {
let newCell = {
return {
is_cell: true,
id: id,
persistentId: "persistent-id",
executionCount: executionCount,
text: codeLines.join('\n'),
hasError: false,
isCode: true,
outputs: [],
gathered: false,
copy: () => newCell,
toJupyterJSON: () => {}
copy: () => null,
serialize: () => null
};
return newCell;
}
function cellSlice(cell: ICell, slice: LocationSet): CellSlice {

Просмотреть файл

@ -8,7 +8,7 @@ describe('program builder', () => {
function createCell(id: string, executionCount: number, ...codeLines: string[]): ICell {
let text = codeLines.join("\n");
return { is_cell: true, id, executionCount, persistentId: "persistent-id", text: text,
hasError: false, isCode: true, gathered: false, copy: () => null, toJupyterJSON: () => {} };
hasError: false, gathered: false, outputs: [], copy: () => null, serialize: () => null };
}
let programBuilder: ProgramBuilder;

Просмотреть файл

@ -2,33 +2,12 @@ import { ISettingRegistry } from "@jupyterlab/coreutils";
import { JSONExt, JSONObject } from "@phosphor/coreutils";
import * as $ from "jquery";
/**
* Interface for calling Ajax.
*/
export interface AjaxCaller {
ajax: (
url: string,
settings: {
data: string,
method: string,
error: (_: any, textStatus: string, errorThrown: string) => void
}) => void;
}
/**
* Utility for calling Jupyter server using AJAX.
*/
// let _ajaxCaller: AjaxCaller = undefined;
let _settingRegistry: ISettingRegistry;
let _showedLogUninitializedError = false;
/**
* Initialize logger with Ajax method. The reason we can't just use the default jQuery AJAX
* logger is that notebook requires requests with XSRF tokens. The right Ajax caller is the one
* that's built into Jupyter notebook or lab that passes these tokens.
* Initialize logger with the settings registry, so that the logger can access the user's ID.
*/
export function initLogger(settingRegistry: ISettingRegistry) {
// _ajaxCaller = ajaxCaller;
_settingRegistry = settingRegistry;
}
@ -52,24 +31,18 @@ export function registerPollers(...pollers: IStatePoller[]) {
}
/**
* Log pretty much any data. Fail silently if the request can't be completed (i.e. if the plugin
* for logging is down). Must initialize logger with `initLogger` before calling this method.
* Call this after a batch of operations instead of each item, as calls can take a while.
* Log pretty much any data. Fail silently if the request can't be completed (i.e. if the user's
* computer is not connnected to the internet). Must initialize logger with `initLogger` before
* calling this method. Call this after a batch of operations instead of each item, as calls
* can take a while.
* TODO(andrewhead): save events to a localstorage backlog if the request failed, and attempt to
* log the event when connected to the internet again.
*/
export function log(eventName: string, data?: any) {
data = data || {};
let _ajaxCaller = $;
let LOG_ENDPOINT = 'https://clarence.eecs.berkeley.edu';
if (_ajaxCaller == undefined) {
if (_showedLogUninitializedError == false) {
console.info("Logger not initialized, skipping logging");
_showedLogUninitializedError = true;
}
return;
}
// Prepare log data.
let postData: any = {
@ -102,9 +75,9 @@ export function log(eventName: string, data?: any) {
postData.data = JSON.stringify(postData.data);
// Submit data to logger endpoint.
_ajaxCaller.ajax(LOG_ENDPOINT + "/save", {
$.ajax(LOG_ENDPOINT + "/save", {
data: JSON.parse(JSON.stringify(postData)),
data: postData,
method: "POST",
error: (_: any, textStatus: string, errorThrown: string) => {
console.error("Failed to log", textStatus, errorThrown);