import { actions , selectors } from "@nteract/core" ; import { sendNotification } from "@nteract/mythic-notifications" ; import { openExternalUrl } from "@nteract/mythic-windowing" ; import * as path from "path" ; import { ofType , StateObservable } from "redux-observable" ; import { concat , EMPTY , Observable , of } from "rxjs" ; import { ajax , AjaxResponse } from "rxjs/ajax" ; import { catchError , mergeMap } from "rxjs/operators" ; import { DesktopNotebookAppState } from "../state" ; interface GithubFiles { [ result ] : { content : string ; filename ? : string ; } | null ; } function publishGist ( model , token , id ) { const url = id !== null ? `https://api.github.com/gists/${ id }` : "https://api.github.com/gists" ; const opts = { url , responseType : "json" , createXHR : ( ) => new XMLHttpRequest ( ) , headers : { "Content-Type" : "application/json" , Authorization : `token ${ token }` } , method : id !== null ? "PATCH" : "POST" , body : model } ; return ajax ( opts ) ; } /**
 * Epic to capture the end to end action of publishing and receiving the
 * response from the Github API.
 */ export const publishEpic = ( action$ , state$ ) => { return action$ . pipe ( ofType ( actions . PUBLISH_GIST ) , mergeMap ( ( action ) => { const state = state$ . value ; const contentRef = action . payload . contentRef ; if ( ! contentRef ) { return EMPTY ; } const content = selectors . content ( state , { contentRef } ) ; if ( ! content || content . type !== "notebook" ) { return EMPTY ; } const filepath = content . filepath ; const model = content . model ; const notebookString = selectors . notebook . asString ( model ) ; const gistId = selectors . notebook . gistId ( model ) ; const githubToken = state . app . get ( "githubToken" ) ; if ( githubToken == null ) { return of ( actions . coreError ( new Error ( "need a github token in order to publish" ) ) ) ; } if ( gistId && typeof gistId !== "string" ) { return of ( actions . coreError ( new Error ( "gist id in notebook is not a string" ) ) ) ; } const filename = filepath ? path . parse ( filepath ) . base : "Untitled.ipynb" ; const files = { [ filename ] : { content : notebookString } } ; return concat ( of ( sendNotification . create ( { key : "github-publish" , icon : "book" , title : "Publishing Gist" , message : gistId ? "Updating Gist... 💖📓💖" : "Publishing a new Gist... ✨📓✨" , level : "in-progress" , } ) ) , publishGist ( { files , description : filename , public : false } , githubToken , gistId ) . pipe ( mergeMap ( xhr => of ( actions . overwriteMetadataField ( { field : "github_username" , value : xhr . response . login , contentRef , } ) , actions . overwriteMetadataField ( { field : "gist_id" , value : xhr . response . id , contentRef , } ) , sendNotification . create ( { key : "github-publish" , title : "Publishing Gist" , message : "Gist uploaded 📓📢" , level : "success" , action : { label : "Open" , callback : ( ) => window . store . dispatch ( openExternalUrl . create ( `https://nbviewer.jupyter.org/${ xhr . response . id }` ) ) , } , } ) ) ) , catchError ( err => { const arr = err . xhr . getAllResponseHeaders ( ) . split ( "\r\n" ) ; const headers = arr . reduce ( ( acc , current ) => { const parts = current . split ( ": " ) ; acc [ parts [ 0 ] ] = parts [ 1 ] ; return acc ; } , { } ) ; if ( headers . hasOwnProperty ( "X-OAuth-Scopes" ) && ! headers [ "X-OAuth-Scopes" ] . includes ( "gist" ) ) { return of ( actions . coreError ( new Error ( "Bad GitHub Token: the gist API reports that the token " + "doesn't have gist scopes 🤷‍♀️" ) ) ) ; } if ( err . status >= 500 ) { return of ( actions . coreError ( new Error ( "Gist publishing failed: 😩 GitHub API not feelin' good today" ) ) ) ; } return of ( actions . coreError ( err ) ) ; } ) ) ) ; } ) ) ; } ;