Merge branch 'master' into replace-codemirror-with-monaco
This commit is contained in:
commit
77f895d343
|
@ -98,7 +98,7 @@ export class NotebookComponentBootstrapper {
|
||||||
actions.fetchContentFulfilled({
|
actions.fetchContentFulfilled({
|
||||||
filepath: undefined,
|
filepath: undefined,
|
||||||
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content),
|
model: NotebookComponentBootstrapper.wrapModelIntoContent(name, undefined, content),
|
||||||
kernelRef: createKernelRef(),
|
kernelRef: undefined, // must be undefined or it will be auto-started by the epic
|
||||||
contentRef: this.contentRef
|
contentRef: this.contentRef
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import * as Immutable from "immutable";
|
import * as Immutable from "immutable";
|
||||||
import { ActionsObservable, StateObservable } from "redux-observable";
|
import { ActionsObservable, StateObservable } from "redux-observable";
|
||||||
import { Subject } from "rxjs";
|
import { Subject, empty } from "rxjs";
|
||||||
import { toArray } from "rxjs/operators";
|
import { toArray } from "rxjs/operators";
|
||||||
import { makeNotebookRecord } from "@nteract/commutable";
|
import { makeNotebookRecord } from "@nteract/commutable";
|
||||||
import { actions, state } from "@nteract/core";
|
import { actions, state } from "@nteract/core";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
|
|
||||||
import { CdbAppState, makeCdbRecord } from "./types";
|
import { CdbAppState, makeCdbRecord } from "./types";
|
||||||
import { launchWebSocketKernelEpic } from "./epics";
|
import { launchWebSocketKernelEpic, autoStartKernelEpic } from "./epics";
|
||||||
import { NotebookUtil } from "../NotebookUtil";
|
import { NotebookUtil } from "../NotebookUtil";
|
||||||
|
|
||||||
import { sessions } from "rx-jupyter";
|
import { sessions } from "rx-jupyter";
|
||||||
|
@ -74,46 +74,47 @@ describe("Extract kernel from notebook", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
app: state.makeAppRecord({
|
||||||
|
host: state.makeJupyterHostRecord({
|
||||||
|
type: "jupyter",
|
||||||
|
token: "eh",
|
||||||
|
basePath: "/"
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
comms: state.makeCommsRecord(),
|
||||||
|
config: Immutable.Map({}),
|
||||||
|
core: state.makeStateRecord({
|
||||||
|
kernelRef: "fake",
|
||||||
|
entities: state.makeEntitiesRecord({
|
||||||
|
contents: state.makeContentsRecord({
|
||||||
|
byRef: Immutable.Map({
|
||||||
|
fakeContentRef: state.makeNotebookContentRecord()
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
kernels: state.makeKernelsRecord({
|
||||||
|
byRef: Immutable.Map({
|
||||||
|
fake: state.makeRemoteKernelRecord({
|
||||||
|
type: "websocket",
|
||||||
|
channels: new Subject<any>(),
|
||||||
|
kernelSpecName: "fancy",
|
||||||
|
id: "0"
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
cdb: makeCdbRecord({
|
||||||
|
databaseAccountName: "dbAccountName",
|
||||||
|
defaultExperience: "defaultExperience"
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
describe("launchWebSocketKernelEpic", () => {
|
describe("launchWebSocketKernelEpic", () => {
|
||||||
const createSpy = sinon.spy(sessions, "create");
|
const createSpy = sinon.spy(sessions, "create");
|
||||||
|
|
||||||
const contentRef = "fakeContentRef";
|
const contentRef = "fakeContentRef";
|
||||||
const kernelRef = "fake";
|
const kernelRef = "fake";
|
||||||
const initialState = {
|
|
||||||
app: state.makeAppRecord({
|
|
||||||
host: state.makeJupyterHostRecord({
|
|
||||||
type: "jupyter",
|
|
||||||
token: "eh",
|
|
||||||
basePath: "/"
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
comms: state.makeCommsRecord(),
|
|
||||||
config: Immutable.Map({}),
|
|
||||||
core: state.makeStateRecord({
|
|
||||||
kernelRef: "fake",
|
|
||||||
entities: state.makeEntitiesRecord({
|
|
||||||
contents: state.makeContentsRecord({
|
|
||||||
byRef: Immutable.Map({
|
|
||||||
fakeContentRef: state.makeNotebookContentRecord()
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
kernels: state.makeKernelsRecord({
|
|
||||||
byRef: Immutable.Map({
|
|
||||||
fake: state.makeRemoteKernelRecord({
|
|
||||||
type: "websocket",
|
|
||||||
channels: new Subject<any>(),
|
|
||||||
kernelSpecName: "fancy",
|
|
||||||
id: "0"
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
cdb: makeCdbRecord({
|
|
||||||
databaseAccountName: "dbAccountName",
|
|
||||||
defaultExperience: "defaultExperience"
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
it("launches remote kernels", async () => {
|
it("launches remote kernels", async () => {
|
||||||
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||||
|
@ -490,3 +491,55 @@ describe("launchWebSocketKernelEpic", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("autoStartKernelEpic", () => {
|
||||||
|
const contentRef = "fakeContentRef";
|
||||||
|
const kernelRef = "fake";
|
||||||
|
|
||||||
|
it("automatically starts kernel when content fetch is successful if kernelRef is defined", async () => {
|
||||||
|
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||||
|
|
||||||
|
const action$ = ActionsObservable.of(
|
||||||
|
actions.fetchContentFulfilled({
|
||||||
|
contentRef,
|
||||||
|
kernelRef,
|
||||||
|
filepath: "filepath",
|
||||||
|
model: {}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const responseActions = await autoStartKernelEpic(action$, state$)
|
||||||
|
.pipe(toArray())
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
expect(responseActions).toMatchObject([
|
||||||
|
{
|
||||||
|
type: actions.RESTART_KERNEL,
|
||||||
|
payload: {
|
||||||
|
contentRef,
|
||||||
|
kernelRef,
|
||||||
|
outputHandling: "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Don't start kernel when content fetch is successful if kernelRef is not defined", async () => {
|
||||||
|
const state$ = new StateObservable(new Subject<CdbAppState>(), initialState);
|
||||||
|
|
||||||
|
const action$ = ActionsObservable.of(
|
||||||
|
actions.fetchContentFulfilled({
|
||||||
|
contentRef,
|
||||||
|
kernelRef: undefined,
|
||||||
|
filepath: "filepath",
|
||||||
|
model: {}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const responseActions = await autoStartKernelEpic(action$, state$)
|
||||||
|
.pipe(toArray())
|
||||||
|
.toPromise();
|
||||||
|
|
||||||
|
expect(responseActions).toMatchObject([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { empty, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
import { EMPTY, merge, of, timer, concat, Subject, Subscriber, Observable, Observer } from "rxjs";
|
||||||
import { webSocket } from "rxjs/webSocket";
|
import { webSocket } from "rxjs/webSocket";
|
||||||
import { ActionsObservable, StateObservable } from "redux-observable";
|
import { ActionsObservable, StateObservable } from "redux-observable";
|
||||||
import { ofType } from "redux-observable";
|
import { ofType } from "redux-observable";
|
||||||
|
@ -77,7 +77,7 @@ const addInitialCodeCellEpic = (
|
||||||
|
|
||||||
// If it's not a notebook, we shouldn't be here
|
// If it's not a notebook, we shouldn't be here
|
||||||
if (!model || model.type !== "notebook") {
|
if (!model || model.type !== "notebook") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellOrder = selectors.notebook.cellOrder(model);
|
const cellOrder = selectors.notebook.cellOrder(model);
|
||||||
|
@ -90,7 +90,40 @@ const addInitialCodeCellEpic = (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return empty();
|
return EMPTY;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically start kernel if kernelRef is present.
|
||||||
|
* The kernel is normally lazy-started when a cell is being executed, but a running kernel is
|
||||||
|
* required for code completion to work.
|
||||||
|
* For notebook viewer, there is no kernel
|
||||||
|
* @param action$
|
||||||
|
* @param state$
|
||||||
|
*/
|
||||||
|
export const autoStartKernelEpic = (
|
||||||
|
action$: ActionsObservable<actions.FetchContentFulfilled>,
|
||||||
|
state$: StateObservable<AppState>
|
||||||
|
): Observable<{} | actions.CreateCellBelow> => {
|
||||||
|
return action$.pipe(
|
||||||
|
ofType(actions.FETCH_CONTENT_FULFILLED),
|
||||||
|
mergeMap(action => {
|
||||||
|
const state = state$.value;
|
||||||
|
const { contentRef, kernelRef } = action.payload;
|
||||||
|
|
||||||
|
if (!kernelRef) {
|
||||||
|
return EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return of(
|
||||||
|
actions.restartKernel({
|
||||||
|
contentRef,
|
||||||
|
kernelRef,
|
||||||
|
outputHandling: "None"
|
||||||
|
})
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -288,7 +321,7 @@ export const launchWebSocketKernelEpic = (
|
||||||
const state = state$.value;
|
const state = state$.value;
|
||||||
const host = selectors.currentHost(state);
|
const host = selectors.currentHost(state);
|
||||||
if (host.type !== "jupyter") {
|
if (host.type !== "jupyter") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||||
serverConfig.userPuid = getUserPuid();
|
serverConfig.userPuid = getUserPuid();
|
||||||
|
@ -299,7 +332,7 @@ export const launchWebSocketKernelEpic = (
|
||||||
|
|
||||||
const content = selectors.content(state, { contentRef });
|
const content = selectors.content(state, { contentRef });
|
||||||
if (!content || content.type !== "notebook") {
|
if (!content || content.type !== "notebook") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
let kernelSpecToLaunch = kernelSpecName;
|
let kernelSpecToLaunch = kernelSpecName;
|
||||||
|
@ -513,26 +546,26 @@ const changeWebSocketKernelEpic = (
|
||||||
const state = state$.value;
|
const state = state$.value;
|
||||||
const host = selectors.currentHost(state);
|
const host = selectors.currentHost(state);
|
||||||
if (host.type !== "jupyter") {
|
if (host.type !== "jupyter") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
const serverConfig: NotebookServiceConfig = selectors.serverConfig(host);
|
||||||
if (!oldKernelRef) {
|
if (!oldKernelRef) {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
|
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
|
||||||
if (!oldKernel || oldKernel.type !== "websocket") {
|
if (!oldKernel || oldKernel.type !== "websocket") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
const { sessionId } = oldKernel;
|
const { sessionId } = oldKernel;
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = selectors.content(state, { contentRef });
|
const content = selectors.content(state, { contentRef });
|
||||||
if (!content || content.type !== "notebook") {
|
if (!content || content.type !== "notebook") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
const {
|
const {
|
||||||
filepath,
|
filepath,
|
||||||
|
@ -593,7 +626,7 @@ const focusInitialCodeCellEpic = (
|
||||||
|
|
||||||
// If it's not a notebook, we shouldn't be here
|
// If it's not a notebook, we shouldn't be here
|
||||||
if (!model || model.type !== "notebook") {
|
if (!model || model.type !== "notebook") {
|
||||||
return empty();
|
return EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellOrder = selectors.notebook.cellOrder(model);
|
const cellOrder = selectors.notebook.cellOrder(model);
|
||||||
|
@ -608,7 +641,7 @@ const focusInitialCodeCellEpic = (
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return empty();
|
return EMPTY;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -661,7 +694,7 @@ const notificationsToUserEpic = (
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return empty();
|
return EMPTY;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -701,7 +734,7 @@ const handleKernelConnectionLostEpic = (
|
||||||
if (explorer) {
|
if (explorer) {
|
||||||
explorer.showOkModalDialog("kernel restarts", msg);
|
explorer.showOkModalDialog("kernel restarts", msg);
|
||||||
}
|
}
|
||||||
return of(empty());
|
return of(EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
return concat(
|
return concat(
|
||||||
|
@ -814,7 +847,7 @@ const closeUnsupportedMimetypesEpic = (
|
||||||
explorer.showOkModalDialog("File cannot be rendered", msg);
|
explorer.showOkModalDialog("File cannot be rendered", msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
}
|
}
|
||||||
return empty();
|
return EMPTY;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -842,13 +875,14 @@ const closeContentFailedToFetchEpic = (
|
||||||
explorer.showOkModalDialog("Failure to load", msg);
|
explorer.showOkModalDialog("Failure to load", msg);
|
||||||
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
|
||||||
}
|
}
|
||||||
return empty();
|
return EMPTY;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const allEpics = [
|
export const allEpics = [
|
||||||
addInitialCodeCellEpic,
|
addInitialCodeCellEpic,
|
||||||
|
autoStartKernelEpic,
|
||||||
focusInitialCodeCellEpic,
|
focusInitialCodeCellEpic,
|
||||||
notificationsToUserEpic,
|
notificationsToUserEpic,
|
||||||
launchWebSocketKernelEpic,
|
launchWebSocketKernelEpic,
|
||||||
|
|
Loading…
Reference in New Issue