@nteract/epics
Version:
Redux-Observable epics for nteract apps
331 lines • 14.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.restartWebSocketKernelEpic = exports.killKernelEpic = exports.interruptKernelEpic = exports.changeWebSocketKernelEpic = exports.launchWebSocketKernelEpic = void 0;
const messaging_1 = require("@nteract/messaging");
const redux_observable_1 = require("redux-observable");
const rx_jupyter_1 = require("rx-jupyter");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const actions = __importStar(require("@nteract/actions"));
const selectors = __importStar(require("@nteract/selectors"));
const types_1 = require("@nteract/types");
const types_2 = require("@nteract/types");
const kernel_lifecycle_1 = require("./kernel-lifecycle");
exports.launchWebSocketKernelEpic = (action$, state$) => action$.pipe(redux_observable_1.ofType(actions.LAUNCH_KERNEL_BY_NAME),
// Only accept jupyter servers for the host with this epic
operators_1.filter(() => selectors.isCurrentHostJupyter(state$.value)),
// TODO: When a switchMap happens, we need to close down the originating
// kernel, likely by sending a different action. Right now this gets
// coordinated in a different way.
operators_1.switchMap((action) => {
const state = state$.value;
const host = selectors.currentHost(state);
if (host.type !== "jupyter") {
// Dismiss any usage that isn't targeting a jupyter server
return rxjs_1.empty();
}
const serverConfig = selectors.serverConfig(host);
const hostRef = selectors.hostRefByHostRecord(state, { host });
const { payload: { kernelSpecName, cwd, kernelRef, contentRef } } = action;
const content = selectors.content(state, { contentRef });
if (!content || content.type !== "notebook") {
return rxjs_1.empty();
}
// TODO: Create a START_SESSION action instead (?)
const sessionPayload = {
kernel: {
id: null,
name: kernelSpecName
},
name: "",
// TODO: Figure where the leading slash comes from in the content store
path: content.filepath.replace(/^\/+/g, ""),
type: "notebook"
};
// TODO: Handle failure cases here
return rx_jupyter_1.sessions.create(serverConfig, sessionPayload).pipe(operators_1.mergeMap(data => {
const session = data.response;
const sessionId = types_1.castToSessionId(kernelRef);
const remoteSessionId = types_1.castToSessionId(session.id);
const kernel = Object.assign({}, session.kernel, {
type: "websocket",
info: null,
sessionId,
remoteSessionId,
cwd,
channels: rx_jupyter_1.kernels.connect(serverConfig, session.kernel.id, sessionId),
kernelSpecName,
hostRef,
status: session.kernel.execution_state
});
kernel.channels.next(messaging_1.kernelInfoRequest());
return rxjs_1.of(actions.launchKernelSuccessful({
kernel,
kernelRef,
contentRef: action.payload.contentRef,
selectNextKernel: true
}));
}), operators_1.catchError(error => {
return rxjs_1.of(actions.launchKernelFailed({ error }));
}));
}));
exports.changeWebSocketKernelEpic = (action$, state$) => action$.pipe(redux_observable_1.ofType(actions.CHANGE_KERNEL_BY_NAME),
// Only accept jupyter servers for the host with this epic
operators_1.filter(() => selectors.isCurrentHostJupyter(state$.value)),
// TODO: When a switchMap happens, we need to close down the originating
// kernel, likely by sending a different action. Right now this gets
// coordinated in a different way.
operators_1.switchMap((action) => {
const { payload: { contentRef, oldKernelRef, kernelSpecName } } = action;
const state = state$.value;
const host = selectors.currentHost(state);
if (host.type !== "jupyter") {
// Dismiss any usage that isn't targeting a jupyter server
return rxjs_1.empty();
}
const serverConfig = selectors.serverConfig(host);
// TODO: This is the case where we didn't have a kernel before
// and they chose to switch kernels. Instead we need to allow
// "switching" by disregarding the previous kernel and creating a
// new session
if (!oldKernelRef) {
return rxjs_1.empty();
}
const oldKernel = selectors.kernel(state, { kernelRef: oldKernelRef });
if (!oldKernel || oldKernel.type !== "websocket") {
return rxjs_1.empty();
}
const { sessionId, remoteSessionId } = oldKernel;
if (!sessionId || !remoteSessionId) {
return rxjs_1.empty();
}
const content = selectors.content(state, { contentRef });
if (!content || content.type !== "notebook") {
return rxjs_1.empty();
}
const { filepath, model: { notebook } } = content;
const { cwd } = kernel_lifecycle_1.extractNewKernel(filepath, notebook);
const kernelRef = types_2.createKernelRef();
return rx_jupyter_1.kernels.start(serverConfig, kernelSpecName, cwd).pipe(operators_1.mergeMap(({ response }) => {
const { id: kernelId } = response;
const sessionPayload = {
kernel: { id: kernelId, name: kernelSpecName }
};
// The sessions API will close down the old kernel for us if it is
// on this session
return rx_jupyter_1.sessions.update(serverConfig, remoteSessionId, sessionPayload).pipe(operators_1.mergeMap(({ response: session }) => {
const kernel = Object.assign({}, session.kernel, {
type: "websocket",
sessionId,
remoteSessionId,
cwd,
channels: rx_jupyter_1.kernels.connect(serverConfig, session.kernel.id, sessionId),
kernelSpecName
});
return rxjs_1.of(actions.launchKernelSuccessful({
kernel,
kernelRef,
contentRef: action.payload.contentRef,
selectNextKernel: true
}));
}), operators_1.catchError(error => rxjs_1.of(actions.launchKernelFailed({ error, kernelRef, contentRef }))));
}), operators_1.catchError(error => rxjs_1.of(actions.launchKernelFailed({ error, kernelRef, contentRef }))));
}));
exports.interruptKernelEpic = (action$, state$) => action$.pipe(redux_observable_1.ofType(actions.INTERRUPT_KERNEL),
// This epic can only interrupt kernels on jupyter websockets
operators_1.filter(() => selectors.isCurrentHostJupyter(state$.value)),
// If the user fires off _more_ interrupts, we shouldn't interrupt the in-flight
// interrupt, instead doing it after the last one happens
operators_1.concatMap((action) => {
const state = state$.value;
const host = selectors.currentHost(state);
if (host.type !== "jupyter") {
// Dismiss any usage that isn't targeting a jupyter server
return rxjs_1.empty();
}
const serverConfig = selectors.serverConfig(host);
const { contentRef } = action.payload;
let kernel;
if (contentRef) {
kernel = selectors.kernelByContentRef(state$.value, {
contentRef
});
}
else {
kernel = selectors.currentKernel(state$.value);
}
if (!kernel) {
return rxjs_1.of(actions.interruptKernelFailed({
error: new Error("Can't interrupt a kernel we don't have"),
kernelRef: action.payload.kernelRef
}));
}
if (kernel.type !== "websocket") {
return rxjs_1.of(actions.interruptKernelFailed({
error: new Error("Invalid kernel type for interrupting"),
kernelRef: action.payload.kernelRef
}));
}
if (!kernel.id) {
return rxjs_1.of(actions.interruptKernelFailed({
error: new Error("Kernel does not have ID set"),
kernelRef: action.payload.kernelRef
}));
}
const id = kernel.id;
return rx_jupyter_1.kernels.interrupt(serverConfig, id).pipe(operators_1.map(() => actions.interruptKernelSuccessful({
kernelRef: action.payload.kernelRef,
contentRef
})), operators_1.catchError(err => rxjs_1.of(actions.interruptKernelFailed({
error: err,
kernelRef: action.payload.kernelRef
}))));
}));
exports.killKernelEpic = (action$, state$) =>
// TODO: Use the sessions API for this
action$.pipe(redux_observable_1.ofType(actions.KILL_KERNEL),
// This epic can only interrupt kernels on jupyter websockets
operators_1.filter(() => selectors.isCurrentHostJupyter(state$.value)),
// If the user fires off _more_ kills, we shouldn't interrupt the in-flight
// kill, instead doing it after the last one happens
operators_1.concatMap((action) => {
const state = state$.value;
const host = selectors.currentHost(state);
if (host.type !== "jupyter") {
// Dismiss any usage that isn't targeting a jupyter server
return rxjs_1.empty();
}
const serverConfig = selectors.serverConfig(host);
const { contentRef, kernelRef } = action.payload;
let kernel;
if (contentRef) {
kernel = selectors.kernelByContentRef(state, { contentRef });
}
else if (kernelRef) {
kernel = selectors.kernel(state, { kernelRef });
}
else {
kernel = selectors.currentKernel(state);
}
if (!kernel) {
return rxjs_1.of(actions.killKernelFailed({
error: new Error("kernel not available for killing"),
kernelRef
}));
}
if (kernel.type !== "websocket") {
return rxjs_1.of(actions.killKernelFailed({
error: new Error("websocket kernel epic can only kill websocket kernels with an id"),
kernelRef: action.payload.kernelRef
}));
}
if (!kernel.id || !kernel.remoteSessionId) {
return rxjs_1.of(actions.killKernelFailed({
error: new Error("websocket kernel epic can only kill websocket kernels with an id"),
kernelRef: action.payload.kernelRef
}));
}
// TODO: If this was a kernel language change, we shouldn't be using this
// kill kernel epic because we need to make sure that creation happens
// after deletion
return rx_jupyter_1.sessions.destroy(serverConfig, kernel.remoteSessionId).pipe(operators_1.mergeMap(() => action.payload.dispose && action.payload.kernelRef
? rxjs_1.of(actions.killKernelSuccessful({
kernelRef: action.payload.kernelRef
}), actions.disposeKernel({ kernelRef: action.payload.kernelRef }))
: rxjs_1.of(actions.killKernelSuccessful({
kernelRef: action.payload.kernelRef
}))), operators_1.catchError(err => rxjs_1.of(actions.killKernelFailed({
error: err,
kernelRef: action.payload.kernelRef
}))));
}));
exports.restartWebSocketKernelEpic = (action$, state$) => action$.pipe(redux_observable_1.ofType(actions.RESTART_KERNEL), operators_1.concatMap((action) => {
const state = state$.value;
const { contentRef, outputHandling } = action.payload;
const kernelRef = selectors.kernelRefByContentRef(state, { contentRef }) ||
action.payload.kernelRef;
/**
* If there is still no KernelRef, then throw an error.
*/
if (!kernelRef) {
return rxjs_1.of(actions.restartKernelFailed({
error: new Error("Can't execute restart without kernel ref."),
kernelRef: "none provided",
contentRef
}));
}
const host = selectors.currentHost(state);
if (host.type !== "jupyter") {
return rxjs_1.of(actions.restartKernelFailed({
error: new Error("Can't restart a kernel with no Jupyter host."),
kernelRef,
contentRef
}));
}
const serverConfig = selectors.serverConfig(host);
const kernel = selectors.kernel(state, { kernelRef });
if (!kernel) {
return rxjs_1.of(actions.restartKernelFailed({
error: new Error("Can't restart a kernel that does not exist."),
kernelRef,
contentRef
}));
}
if (kernel.type !== "websocket" || !kernel.id) {
return rxjs_1.of(actions.restartKernelFailed({
error: new Error("Can only restart Websocket kernels via API."),
kernelRef,
contentRef
}));
}
const id = kernel.id;
return rx_jupyter_1.kernels.restart(serverConfig, id).pipe(operators_1.mergeMap((response) => {
if (response.status !== 200) {
return rxjs_1.of(actions.restartKernelFailed({
error: new Error("Unsuccessful kernel restart."),
kernelRef,
contentRef
}));
}
else {
if (outputHandling === "Run All") {
return rxjs_1.of(actions.restartKernelSuccessful({
kernelRef,
contentRef
}), actions.executeAllCells({ contentRef }));
}
else if (outputHandling === "Clear All") {
return rxjs_1.of(actions.restartKernelSuccessful({
kernelRef,
contentRef
}), actions.clearAllOutputs({ contentRef }));
}
else {
return rxjs_1.of(actions.restartKernelSuccessful({
kernelRef,
contentRef
}));
}
}
}), operators_1.catchError(error => rxjs_1.of(actions.restartKernelFailed({ error, kernelRef, contentRef }))));
}));
//# sourceMappingURL=websocket-kernel.js.map