diff --git a/src/file/File.container.js b/src/file/File.container.js index 0e0849293b..b95e44d4b6 100644 --- a/src/file/File.container.js +++ b/src/file/File.container.js @@ -100,7 +100,7 @@ class FilePreview extends React.Component { if (this.getFileExtension() === "ipynb") { return Object.freeze(value))} + notebook={JSON.parse(atobUTF8(this.props.file.content))} filePath={this.props.file.file_path} {...this.props} />; diff --git a/src/file/File.present.js b/src/file/File.present.js index 0f22f1bf42..ad97ae17aa 100644 --- a/src/file/File.present.js +++ b/src/file/File.present.js @@ -35,7 +35,6 @@ import { Time } from "../utils/Time"; const commitMessageLengthLimit = 120; - /** * Display the Card with file information. Has the following parameters: * @@ -53,43 +52,49 @@ class FileCard extends React.Component { let commitHeader = null; if (this.props.commit) { const commitLinkHref = `${this.props.gitLabUrl}/commit/${this.props.commit.id}`; - const title = (this.props.commit.title.length > commitMessageLengthLimit) ? - this.props.commit.title.slice(0, commitMessageLengthLimit) + "..." : - this.props.commit.title; - commitHeader = - -
-
- - Commit: {this.props.commit.short_id} -   - {title} -
-
- {this.props.commit.author_name}   - {Time.toIsoString(this.props.commit.committed_date)} + const title = + this.props.commit.title.length > commitMessageLengthLimit + ? this.props.commit.title.slice(0, commitMessageLengthLimit) + "..." + : this.props.commit.title; + commitHeader = ( + + +
+ +
+ {this.props.commit.author_name}   + {Time.toIsoString(this.props.commit.committed_date)} +
-
- - ; + + + ); } - return - - {this.props.lfsBadge} - {this.props.filePath} -   - -   -  File view -
- {this.props.buttonJupyter} - {this.props.buttonGit} - {this.props.buttonGraph} -
-
- {commitHeader} - {this.props.body} -
; + return ( + + + {this.props.lfsBadge} + {this.props.filePath} +   + +   +  File view +
+ {this.props.buttonJupyter} + {this.props.buttonGit} + {this.props.buttonGraph} +
+
+ {commitHeader} + {this.props.body} +
+ ); } } @@ -106,57 +111,97 @@ class FileCard extends React.Component { * @param {Object} error - The error object from GitLab (can be null) */ class ShowFile extends React.Component { - render() { const gitLabFilePath = this.props.gitLabFilePath; - const buttonGraph = this.props.lineagesPath !== undefined ? - - : null; - - const buttonGit = ; + const buttonGraph = + this.props.lineagesPath !== undefined ? ( + + ) : null; + + const buttonGit = ( + + ); if (this.props.error !== null) { - return ; + return ( + + ); } if (this.props.file == null) { - return -   - {"Loading..."} - ; + return ( + +   + {"Loading..."} + + ); } const isLFS = this.props.hashElement ? this.props.hashElement.isLfs : false; - const isLFSBadge = isLFS ? - LFS : - null; - - const body = ; - - return ; + const isLFSBadge = isLFS ? ( + + LFS + + ) : null; + + const body = ; + + return ( + + ); } } -class StyledNotebook extends React.Component { +/** + * Modify the notebook metadata so it is correctly processed by the renderer. + * + * @param {object} [nb] - The notebook to process + */ +function tweakCellMetadata(nb) { + // Scan the cell metadata, and, if jupyter.source_hidden === true, set hide_input = true + const result = { ...nb }; + result.cells = []; + nb.cells.forEach(cell => { + if (cell.metadata.jupyter == null) { + result.cells.push(cell); + } + else { + const clone = { ...cell }; + clone.metadata = { ...cell.metadata }; + clone.metadata.hide_input = clone.metadata.jupyter.source_hidden; + result.cells.push(clone); + } + }); + return result; +} +class StyledNotebook extends React.Component { componentDidMount() { // TODO go through the dom and modify the nodes, e.g., with D3 //this.fixUpDom(ReactDOM.findDOMNode(this.notebook)); @@ -164,14 +209,16 @@ class StyledNotebook extends React.Component { render() { if (this.props.notebook == null) return
Loading...
; + const notebook = tweakCellMetadata(this.props.notebook); return [ { this.notebook = c; }} + ref={c => { + this.notebook = c; + }} defaultStyle={false} loadMathjax={false} - notebook={this.props.notebook} - showCode={true} + notebook={notebook} /> ]; } @@ -180,10 +227,9 @@ class StyledNotebook extends React.Component { class JupyterButtonPresent extends React.Component { render() { if (!this.props.access) - return (); + return ; - if (this.props.updating) - return (); + if (this.props.updating) return ; return ( + filePath={this.props.filePath} + /> ); } } -export { StyledNotebook, JupyterButtonPresent, ShowFile }; +export { StyledNotebook, JupyterButtonPresent, ShowFile, tweakCellMetadata }; diff --git a/src/file/File.test.js b/src/file/File.test.js index 40f2ac58f4..784ed76973 100644 --- a/src/file/File.test.js +++ b/src/file/File.test.js @@ -31,7 +31,7 @@ import { testClient as client } from "../api-client"; import { generateFakeUser } from "../user/User.test"; import { ShowFile, JupyterButton } from "./index"; import { StateModel, globalSchema } from "../model"; - +import { tweakCellMetadata } from "./File.present"; const model = new StateModel(globalSchema); @@ -46,7 +46,7 @@ describe("rendering", () => { model, filePath: "/projects/1/files/blob/myFolder/myNotebook.ipynb", match: { url: "/projects/1", params: { id: "1" } }, - launchNotebookUrl: "/projects/1/launchNotebook", + launchNotebookUrl: "/projects/1/launchNotebook" }; for (let user of users) { @@ -54,11 +54,13 @@ describe("rendering", () => { const div = document.createElement("div"); // * fix for tooltips https://github.com/reactstrap/reactstrap/issues/773#issuecomment-357409863 document.body.appendChild(div); - const branches = { all: [], fetch: () => { } }; + const branches = { all: [], fetch: () => {} }; ReactDOM.render( - , div); + , + div + ); }); it(`renders ShowFile for ${user.type} user`, () => { @@ -67,7 +69,146 @@ describe("rendering", () => { ReactDOM.render( - , div); + , + div + ); }); } }); + +describe("cell metadata messaging", () => { + it("leaves cell metadata unmodified if not necessary", () => { + const notebook = { + cells: [ + { + cell_type: "code", + execution_count: 3, + metadata: {}, + outputs: [ + { + name: "stdout", + output_type: "stream", + text: ["show input\n"] + } + ], + source: ['print("show input")'] + } + ], + metadata: { + kernelspec: { + display_name: "Python 3", + language: "python", + name: "python3" + }, + language_info: { + codemirror_mode: { + name: "ipython", + version: 3 + }, + file_extension: ".py", + mimetype: "text/x-python", + name: "python", + nbconvert_exporter: "python", + pygments_lexer: "ipython3", + version: "3.7.4" + } + }, + nbformat: 4, + nbformat_minor: 4 + }; + const result = tweakCellMetadata(notebook); + expect(result).toEqual(notebook); + }); + it("modifies the cells that are necessary", () => { + const notebook = { + cells: [ + { + cell_type: "code", + execution_count: 3, + metadata: {}, + outputs: [ + { + name: "stdout", + output_type: "stream", + text: ["show input\n"] + } + ], + source: ['print("show input")'] + }, + { + cell_type: "code", + execution_count: 4, + metadata: { + jupyter: { + source_hidden: true + }, + papermill: { + duration: 0.48317, + end_time: "2020-03-31T08:27:03.045655", + exception: false, + start_time: "2020-03-31T08:27:02.562485", + status: "completed" + }, + tags: [] + }, + outputs: [ + { + name: "stdout", + output_type: "stream", + text: ["hide input\n"] + } + ], + source: ['print("hide input")'] + }, + { + cell_type: "code", + execution_count: 5, + metadata: { + hide_input: true, + papermill: { + duration: 0.48317, + end_time: "2020-03-31T08:27:03.045655", + exception: false, + start_time: "2020-03-31T08:27:02.562485", + status: "completed" + }, + tags: [] + }, + outputs: [ + { + name: "stdout", + output_type: "stream", + text: ["hide too\n"] + } + ], + source: ['print("hide too")'] + } + ], + metadata: { + kernelspec: { + display_name: "Python 3", + language: "python", + name: "python3" + }, + language_info: { + codemirror_mode: { + name: "ipython", + version: 3 + }, + file_extension: ".py", + mimetype: "text/x-python", + name: "python", + nbconvert_exporter: "python", + pygments_lexer: "ipython3", + version: "3.7.4" + } + }, + nbformat: 4, + nbformat_minor: 4 + }; + const result = tweakCellMetadata(notebook); + expect(result.cells[0]).toEqual(notebook.cells[0]); + expect(notebook.cells[1].metadata.hide_input).toEqual(undefined); + expect(result.cells[1].metadata.hide_input).toEqual(true); + }); +});