catchError((error: Error) => of( actions.saveFailed({ error, contentRef: action.payload.contentRef }) )
test("should set isSaving to false", () => { const originalState = stateModule.makeAppRecord({ isSaving: true }); const state = reducers.app(originalState, actions.saveFailed({})); expect(state.isSaving).toBe(false); });
mergeMap(xhr => { // TODO: What does it mean if we have a failed GET on the content if (xhr.status !== 200) { throw new Error(xhr.response.toString()); } if (typeof xhr.response === "string") { throw new Error( `jupyter server response invalid: ${xhr.response}` ); } const model = xhr.response; const diskDate = new Date(model.last_modified); const inMemoryDate = content.lastSaved ? new Date(content.lastSaved) : // FIXME: I'm unsure if we don't have a date if we should default to the disk date diskDate; const diffDate = diskDate.getTime() - inMemoryDate.getTime(); if (Math.abs(diffDate) > 600) { return of( actions.saveFailed({ error: new Error("open in another tab possibly..."), contentRef: action.payload.contentRef }) ); } return contents.save(serverConfig, filepath, saveModel).pipe( map((xhr: AjaxResponse) => { return actions.saveFulfilled({ contentRef: action.payload.contentRef, model: xhr.response }); }), catchError((error: Error) => of( actions.saveFailed({ error, contentRef: action.payload.contentRef }) ) ) ); })
( action: actions.Save | actions.DownloadContent ): | Observable< | actions.DownloadContentFailed | actions.DownloadContentFulfilled | actions.SaveFailed | actions.SaveFulfilled > | Observable<never> => { const state = state$.value; const host = selectors.currentHost(state); if (host.type !== "jupyter") { // Dismiss any usage that isn't targeting a jupyter server return empty(); } const contentRef = action.payload.contentRef; const content = selectors.content(state, { contentRef }); // NOTE: This could save by having selectors for each model type // have toDisk() selectors // It will need to be cased off when we have more than one type // of content we actually save if (!content) { const errorPayload = { error: new Error("Content was not set."), contentRef: action.payload.contentRef }; if (action.type === actions.DOWNLOAD_CONTENT) { return of(actions.downloadContentFailed(errorPayload)); } return of(actions.saveFailed(errorPayload)); } if (content.type === "directory") { // Don't save directories return empty(); } let filepath = content.filepath; // This could be object for notebook, or string for files let serializedData: Notebook | string; let saveModel: Partial<contents.Payload> = {}; if (content.type === "notebook") { const appVersion = selectors.appVersion(state); // contents API takes notebook as raw JSON whereas downloading takes // a string serializedData = toJS( content.model.notebook.setIn( ["metadata", "nteract", "version"], appVersion ) ); saveModel = { content: serializedData, type: content.type }; } else if (content.type === "file") { serializedData = content.model.text; saveModel = { content: serializedData, type: content.type, format: "text" }; } else { // We shouldn't save directories return empty(); } switch (action.type) { case actions.DOWNLOAD_CONTENT: { // FIXME: Convert this to downloadString, so it works for both files & notebooks if ( content.type === "notebook" && typeof serializedData === "object" ) { downloadString( stringifyNotebook(serializedData), filepath || "notebook.ipynb", "application/json" ); } else if ( content.type === "file" && typeof serializedData === "string" ) { downloadString( serializedData, filepath, content.mimetype || "application/octet-stream" ); } else { // This shouldn't happen, is here for safety return empty(); } return of( actions.downloadContentFulfilled({ contentRef: action.payload.contentRef }) ); } case actions.SAVE: { const serverConfig: ServerConfig = selectors.serverConfig(host); // Check to see if the file was modified since the last time we saved // TODO: Determine how we handle what to do // Don't bother doing this if the file is new(?) return contents.get(serverConfig, filepath, { content: 0 }).pipe( // Make sure that the modified time is within some delta mergeMap(xhr => { // TODO: What does it mean if we have a failed GET on the content if (xhr.status !== 200) { throw new Error(xhr.response); } const model = xhr.response; const diskDate = new Date(model.last_modified); const inMemoryDate = content.lastSaved ? new Date(content.lastSaved) : // FIXME: I'm unsure if we don't have a date if we should default to the disk date diskDate; const diffDate = diskDate.getTime() - inMemoryDate.getTime(); if (Math.abs(diffDate) > 600) { return of( actions.saveFailed({ error: new Error("open in another tab possibly..."), contentRef: action.payload.contentRef }) ); } return contents.save(serverConfig, filepath, saveModel).pipe( map((xhr: AjaxResponse) => { return actions.saveFulfilled({ contentRef: action.payload.contentRef, model: xhr.response }); }), catchError((error: Error) => of( actions.saveFailed({ error, contentRef: action.payload.contentRef }) ) ) ); }) ); } default: // NOTE: Flow types and our ofType should prevent reaching here, this // is here merely as safety return empty(); } }