зеркало из https://github.com/microsoft/gather.git
Bugfix: Gather cell outputs with gathered cells
This commit is contained in:
Родитель
40f356fa1a
Коммит
1f067987da
|
@ -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);
|
||||
|
|
Загрузка…
Ссылка в новой задаче